2 Set up
First of all, start with the required libraries and functions.
library(tidyverse)
library(sf)
library(lubridate)
library(tigris)
library(tidycensus)
library(viridis)
library(riem)
library(gridExtra)
library(knitr)
library(kableExtra)
library(RSocrata)
library(ggplot2)
library(gifski)
library(gganimate)
library(caret)
plotTheme <- theme(
plot.title =element_text(size=12),
plot.subtitle = element_text(size=8),
plot.caption = element_text(size = 6),
axis.text.x = element_text(size = 10, angle = 45, hjust = 1),
axis.text.y = element_text(size = 10),
axis.title.y = element_text(size = 10),
# Set the entire chart region to blank
panel.background=element_blank(),
plot.background=element_blank(),
#panel.border=element_rect(colour="#F0F0F0"),
# Format the grid
panel.grid.major=element_line(colour="#D0D0D0",size=.2),
axis.ticks=element_blank())
mapTheme <- theme(plot.title =element_text(size=12),
plot.subtitle = element_text(size=8),
plot.caption = element_text(size = 6),
axis.line=element_blank(),
axis.text.x=element_blank(),
axis.text.y=element_blank(),
axis.ticks=element_blank(),
axis.title.x=element_blank(),
axis.title.y=element_blank(),
panel.background=element_blank(),
panel.border=element_blank(),
panel.grid.major=element_line(colour = 'transparent'),
panel.grid.minor=element_blank(),
legend.direction = "vertical",
legend.position = "right",
plot.margin = margin(1, 1, 1, 1, 'cm'),
legend.key.height = unit(1, "cm"), legend.key.width = unit(0.2, "cm"))
palette5 <- c("#FB8C6F","#73607D","#C1B9AE","#FDC664","#355C7D")
palette4 <- c("#FB8C6F","#73607D","#C1B9AE","#FDC664")
palette2 <- c("#FB8C6F","#73607D")
2.1 Import bike share data
The LA Metro Bike Share trip data (source:https://bikeshare.metro.net/about/data/) for 2022 Q2 is
read in.
dat <- st_read("./metro-trips-2022-q2.csv")
station_name <- st_read("./metro-bike-share-stations-2022-10-01.csv")
colnames(station_name)[colnames(station_name) == 'Station_ID'] <- "start_station"
dat <-
merge(dat, station_name[c("Station_Name", "start_station")], by = "start_station")
# st_transform('ESRI:102286')
As below, parse_date_time function is used to
standardize the start_time field (when a trip departed)
into the 1 hour and 15 minute intervals needed for later analysis.
Function week and wday convert the data/time
stamp into week of the year and day of the week, respectively.
dat2 <- dat %>%
mutate(interval60 = floor_date(mdy_hm(start_time), unit = "hour"),
interval15 = floor_date(mdy_hm(start_time), unit = "15 mins"),
week = week(interval60),
dotw = wday(interval60, label=TRUE)) %>%
mutate(new_start_time = strptime(as.character(start_time), "%m/%d/%Y %H:%M"),
new_end_time = strptime(as.character(end_time), "%m/%d/%Y %H:%M")) %>%
filter(week>17 & week<23)
glimpse(dat2)
## Rows: 26,989
## Columns: 22
## $ start_station <chr> "3000", "3000", "3005", "3005", "3005", "3005", "3…
## $ trip_id <chr> "193851816", "193855710", "191014162", "194147504"…
## $ duration <chr> "4", "74", "4", "64", "1", "16", "16", "4", "84", …
## $ start_time <chr> "5/27/2022 14:59", "5/27/2022 15:04", "5/1/2022 18…
## $ end_time <chr> "5/27/2022 15:03", "5/27/2022 16:18", "5/1/2022 18…
## $ start_lat <chr> "", "", "34.0485", "34.0485", "34.0485", "34.0485"…
## $ start_lon <chr> "", "", "-118.258537", "-118.258537", "-118.258537…
## $ end_station <chr> "3000", "3000", "3031", "3005", "3005", "3030", "3…
## $ end_lat <chr> "", "", "34.044701", "34.0485", "34.0485", "34.051…
## $ end_lon <chr> "", "", "-118.252441", "-118.258537", "-118.258537…
## $ bike_id <chr> "6335", "6335", "20052", "14901", "12158", "5984",…
## $ plan_duration <chr> "30", "365", "30", "1", "30", "30", "30", "30", "3…
## $ trip_route_category <chr> "Round Trip", "Round Trip", "One Way", "Round Trip…
## $ passholder_type <chr> "Monthly Pass", "Annual Pass", "Monthly Pass", "On…
## $ bike_type <chr> "standard", "standard", "standard", "standard", "s…
## $ Station_Name <chr> "Virtual Station", "Virtual Station", "7th & Flowe…
## $ interval60 <dttm> 2022-05-27 14:00:00, 2022-05-27 15:00:00, 2022-05…
## $ interval15 <dttm> 2022-05-27 14:45:00, 2022-05-27 15:00:00, 2022-05…
## $ week <dbl> 21, 21, 18, 22, 20, 20, 20, 22, 20, 22, 20, 19, 21…
## $ dotw <ord> Fri, Fri, Sun, Mon, Sat, Mon, Thu, Wed, Tue, Tue, …
## $ new_start_time <dttm> 2022-05-27 14:59:00, 2022-05-27 15:04:00, 2022-05…
## $ new_end_time <dttm> 2022-05-27 15:03:00, 2022-05-27 16:18:00, 2022-05…
2.2 Import Census Info
The code block below pulls census geography and some variables from
the tidycensus package. These will be used to demonstrate
generalizability later, but won’t be used as independent variables
because they end up being perfectly colinear with the stations fixed
effects. Spatial information will be added to ridershare data as origin
and destination data.
LACensus <-
get_acs(geography = "tract",
variables = c("B01003_001", "B19013_001",
"B02001_002", "B08013_001",
"B08012_001", "B08301_001",
"B08301_010", "B01002_001"),
year = 2020,
state = "CA",
geometry = TRUE,
county=c("Los Angeles"),
output = "wide") %>%
rename(Total_Pop = B01003_001E,
Med_Inc = B19013_001E,
Med_Age = B01002_001E,
White_Pop = B02001_002E,
Travel_Time = B08013_001E,
Num_Commuters = B08012_001E,
Means_of_Transport = B08301_001E,
Total_Public_Trans = B08301_010E) %>%
select(Total_Pop, Med_Inc, White_Pop, Travel_Time,
Means_of_Transport, Total_Public_Trans,
Med_Age,
GEOID, geometry) %>%
mutate(Percent_White = White_Pop / Total_Pop,
Mean_Commute_Time = Travel_Time / Total_Public_Trans,
Percent_Taking_Public_Trans = Total_Public_Trans / Means_of_Transport)
LATracts <-
LACensus %>%
as.data.frame() %>%
distinct(GEOID, .keep_all = TRUE) %>%
select(GEOID, geometry) %>%
st_sf
dat2$start_lon[dat2$start_lon==''] <- NA
dat2$start_lat[dat2$start_lat==''] <- NA
dat2$end_lon[dat2$end_lon==''] <- NA
dat2$end_lat[dat2$end_lat==''] <- NA
dat_census <- st_join(dat2 %>%
filter(is.na(start_lon) == FALSE &
is.na(start_lat) == FALSE &
is.na(end_lat) == FALSE &
is.na(end_lon) == FALSE )%>%
st_as_sf(., coords = c("start_lon", "start_lat"), crs = 4326),
LATracts %>%
st_transform(crs=4326),
join=st_intersects,
left = TRUE) %>%
rename(Origin.Tract = GEOID) %>%
mutate(start_lon = unlist(map(geometry, 1)),
start_lan = unlist(map(geometry, 2)))%>%
as.data.frame() %>%
select(-geometry)%>%
st_as_sf(., coords = c("end_lon", "end_lat"), crs = 4326) %>%
st_join(., LATracts %>%
st_transform(crs=4326),
join=st_intersects,
left = TRUE) %>%
rename(Destination.Tract = GEOID) %>%
mutate(end_lon = unlist(map(geometry, 1)),
end_lat = unlist(map(geometry, 2)))%>%
as.data.frame() %>%
select(-geometry)
2.3 Import Weather Data
It would be reasonable to anticipate that bad weather in the Windy
City would encourage ride sharing. Here, I imported weather data from
Los Angeles Downtown/USC (SID code: CQT, serving period from 1999 till
now) using riem_measures between 2022-05-01 and
2022-05-31.
Below a weather.Panel is generated to summarize
temperature, precipitation and wind speed for every hour in May
2022.
weather.Panel <-
riem_measures(station = "CQT", date_start = "2022-04-30", date_end = "2022-06-03") %>%
dplyr::select(valid, tmpf, p01i, sknt)%>%
replace(is.na(.), 0) %>%
mutate(interval60 = ymd_h(substr(valid,1,13))) %>%
mutate(week = week(interval60),
dotw = wday(interval60, label=TRUE)) %>%
group_by(interval60) %>%
summarize(Temperature = max(tmpf),
Precipitation = sum(p01i),
Wind_Speed = max(sknt)) %>%
mutate(Temperature = ifelse(Temperature == 0, 42, Temperature))
glimpse(weather.Panel)
## Rows: 816
## Columns: 4
## $ interval60 <dttm> 2022-04-30 00:00:00, 2022-04-30 01:00:00, 2022-04-30 02…
## $ Temperature <dbl> 64.9, 62.1, 60.1, 59.0, 57.9, 57.9, 57.0, 57.0, 55.9, 55…
## $ Precipitation <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ Wind_Speed <dbl> 3, 5, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3,…
Below the weather data is plotted as a time series using
grid.arrange.
grid.arrange(
ggplot(weather.Panel, aes(interval60,Precipitation)) + geom_line() +
labs(title="Percipitation", x="Hour", y="Perecipitation") + plotTheme,
ggplot(weather.Panel, aes(interval60,Wind_Speed)) + geom_line() +
labs(title="Wind Speed", x="Hour", y="Wind Speed") + plotTheme,
ggplot(weather.Panel, aes(interval60,Temperature)) + geom_line() +
labs(title="Temperature", x="Hour", y="Temperature") + plotTheme,
top="Weather Data - LA CQT - May, 2022")

3 Describe and Explore the Data
3.1 Create Space-Time Panel
To make sure that each unique and hour/day combo exists in our data
set, an empty study.panel is created which has each unique
space/time observations. No matter an observation took place or not,
each time period in the study is represented by a row in it.
The maximum number of combinations can be calculated by 24 hours per
day * 7 days per week * 5 weeks * the number of stations. Then, compare
that to the number of rows in study.panel by
nrow. Both steps show the same result: 174528, which means
that the counts is correct.
Continue to join the station name, tract and latitude/longitude.
length(unique(dat_census$interval60)) * length(unique(dat_census$start_station))
## [1] 174528
study.panel <-
expand.grid(interval60=unique(dat_census$interval60),
start_station = unique(dat_census$start_station)) %>%
left_join(., dat_census %>%
select(start_station, Station_Name, Origin.Tract, start_lon, start_lan )%>%
distinct() %>%
group_by(start_station) %>%
slice(1))
nrow(study.panel)
## [1] 174528
In the code below, a full panel is summarizing by station for each
time interval, keep cansus info and latitude/longitude information along
for joining later to other data. Start station IDs are removed by
FALSE.
ride.panel <-
dat_census %>%
mutate(Trip_Counter = 1) %>%
right_join(study.panel) %>%
group_by(interval60, start_station, Station_Name, Origin.Tract, start_lon, start_lan) %>% # missing "from_station_name"
summarize(Trip_Count = sum(Trip_Counter, na.rm=T)) %>%
left_join(weather.Panel) %>%
ungroup() %>%
filter(is.na(start_station) == FALSE) %>%
mutate(week = week(interval60),
dotw = wday(interval60, label = TRUE)) %>%
filter(is.na(Origin.Tract) == FALSE)
ride.panel <-
left_join(ride.panel, LACensus %>%
as.data.frame() %>%
select(-geometry), by = c("Origin.Tract" = "GEOID"))
3.2 Create time lags
ride.panel <-
ride.panel %>%
arrange(start_station, interval60) %>%
mutate(lagHour = dplyr::lag(Trip_Count,1),
lag2Hours = dplyr::lag(Trip_Count,2),
lag3Hours = dplyr::lag(Trip_Count,3),
lag4Hours = dplyr::lag(Trip_Count,4),
lag12Hours = dplyr::lag(Trip_Count,12),
lag1day = dplyr::lag(Trip_Count,24),
holiday = ifelse(yday(interval60) == 148,1,0)) %>%
mutate(day = yday(interval60)) %>%
mutate(holidayLag = case_when(dplyr::lag(holiday, 1) == 1 ~ "PlusOneDay",
dplyr::lag(holiday, 2) == 1 ~ "PlustTwoDays",
dplyr::lag(holiday, 3) == 1 ~ "PlustThreeDays",
dplyr::lead(holiday, 1) == 1 ~ "MinusOneDay",
dplyr::lead(holiday, 2) == 1 ~ "MinusTwoDays",
dplyr::lead(holiday, 3) == 1 ~ "MinusThreeDays"),
holidayLag = ifelse(is.na(holidayLag) == TRUE, 0, holidayLag))
as.data.frame(ride.panel) %>%
group_by(interval60) %>%
summarise_at(vars(starts_with("lag"), "Trip_Count"), mean, na.rm = TRUE) %>%
gather(Variable, Value, -interval60, -Trip_Count) %>%
mutate(Variable = factor(Variable, levels=c("lagHour","lag2Hours","lag3Hours","lag4Hours",
"lag12Hours","lag1day")))%>%
group_by(Variable) %>%
summarize(correlation = round(cor(Value, Trip_Count),2))
## # A tibble: 6 × 2
## Variable correlation
## <fct> <dbl>
## 1 lagHour 0.89
## 2 lag2Hours 0.76
## 3 lag3Hours 0.59
## 4 lag4Hours 0.39
## 5 lag12Hours -0.81
## 6 lag1day 0.83
3.3 Traning/testing data set
Here, a time series approach is taken, a training data set with 3
weeks of data (weeks 18-20) and a testing data set with 2 weeks of data
(weeks 21-22) are created. Code below split the data by
week.
ride.Train <- filter(ride.panel, week >= 18 & week <= 20)
ride.Test <- filter(ride.panel, week >=21 & week <= 22)
3.4 Exploratoring data
In this section, the rider share data is explored for time, space,
weather, and demographic relationships. If the Trip_Count
exhibit serial(temporal) correlation, the time lag features will lead to
better predictions.
3.4.1 Serial autocorrelation
We begin by examining the time and frequency components of our
data.
First, we look at the overall time pattern - there is clearly a daily
periodicity and there are lull periods on weekends.
geo-vline is used to visualize Mondays. All of
the five weeks exhibit similar time series patterns with consistent
peaks and troughs. Which suggests the presence of serial
correlation.
mondays <-
mutate(ride.panel,
monday = ifelse(dotw == "Mon" & hour(interval60) == 1,
interval60, 0)) %>%
filter(monday != 0)
st_drop_geometry(rbind(
mutate(ride.Train, Legend = "Training"),
mutate(ride.Test, Legend = "Testing"))) %>%
group_by(Legend, interval60) %>%
summarize(Trip_Count = sum(Trip_Count)) %>%
ungroup() %>%
ggplot(aes(interval60, Trip_Count, colour = Legend)) + geom_line() +
scale_colour_manual(values = palette2) +
geom_vline(data = mondays, aes(xintercept = monday)) +
labs(title="LA Rideshare trips by week: Apr 30 - Jun 03, 2022",
subtitle="Dotted lines for Mondays",
x="Day", y="Trip Count",
caption = "Figure 3-1") +
plotTheme + theme(panel.grid.major = element_blank())

Next, the time lag features are tested for correlation
with Trip_Count. plotData.lag returns the
Trip_Count and time lag features for week 18.
fct_relevel reorders the lag levels. Omit that line and try
levels(plotData.lag$Variable).
Pearson correlation is then calculated for each Variable
in correlation.lag.
Weak Trip_Count correlations are visualized below.
plotData.lag <-
filter(as.data.frame(ride.panel), week == 18) %>%
dplyr::select(starts_with("lag"), Trip_Count) %>%
gather(Variable, Value, -Trip_Count) %>%
mutate(Variable = fct_relevel(Variable, "lagHour","lag2Hours","lag3Hours",
"lag4Hours","lag12Hours","lag1day"))
correlation.lag <-
group_by(plotData.lag, Variable) %>%
summarize(correlation = round(cor(Value, Trip_Count, use = "complete.obs"), 2))
plotData.lag %>%
ggplot()+
geom_point(aes(x= Value, y = Trip_Count))+
geom_text(data = correlation.lag, aes(label = paste("r =", round(correlation, 2))),
x=-Inf, y=Inf, vjust = 1.5, hjust = -.1) +
geom_smooth(aes(x= Value, y= Trip_Count), method = "lm", se = FALSE, color = "73607D")+
geom_abline(slope = 1, intercept = 0)+
facet_wrap(~Variable, ncol = 2, scales = "free") +
labs(title="Ridershare trip count as a function of time lags",
x="Lag Trip Count",
y="Trip_Count",
caption = "Figure 3-2")+
plotTheme

Figure 3-3 illustrates the distribution of trip volume by station for
different times of the day. There are some stations with high volume
periods, but more have low volume. Therefore, our data will consist of a
number of low demand station/hours and a few high demand station
hours.
We can also track the daily trends in ridership by day of the week
and weekend versus weekday, to see what temporal patterns we’d like to
control for.
dat_census %>%
mutate(time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush"))%>%
group_by(interval60, start_station, time_of_day) %>%
tally()%>%
group_by(start_station, time_of_day)%>%
summarize(mean_trips = mean(n))%>%
ggplot()+
geom_histogram(aes(mean_trips), binwidth = 1)+
labs(title="Mean Number of Hourly Trips Per Station. LA, Apr 30 - Jun 03, 2022",
x="Number of trips",
y="Frequency",
caption = "Figure 3-3")+
facet_wrap(~time_of_day)+
plotTheme

ggplot(dat_census %>%
group_by(interval60, start_station) %>%
tally())+
geom_histogram(aes(n), binwidth = 5)+
labs(title="Bike share trips per hr by station. LA, Apr 30 - Jun 03, 2022",
x="Trip Counts",
y="Number of Stations",
caption = "Figure 3-4")+
plotTheme

Figure 3-5 and 3-6 illustrate total Trip_Counts
distribution in one day by weekdays and weekends. It clear that the trip
volume of each day in a week have similar patterns: reaching peak at
about 400 in 12:00-17:00 and falling at near 0 value in 3:00 or 24:00.
Weekdays have almost double peak volume than weekends.
ggplot(dat_census %>% mutate(hour = hour(new_start_time)))+
geom_freqpoly(aes(hour, color = dotw), binwidth = 1)+
labs(title="Bike share trips in LA, by day of the week, May, 2022",
x="Hour",
y="Trip Counts",
caption = "Figure 3-5")+
plotTheme

ggplot(dat_census %>%
mutate(hour = hour(new_start_time),
weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday")))+
geom_freqpoly(aes(hour, color = weekend), binwidth = 1)+
labs(title="Bike share trips in LA - weekend vs weekday, Apr 30 - Jun 03, 2022",
x="Hour",
y="Trip Counts",
caption = "Figure 3-6")+
plotTheme

3.4.2 Spatial autocorrelation
In this part, the spatial autocorrelation will be examined. Figure
3-7 shows the Trip-Count per hour by station in weekdays
and weekends, respectively.
ggplot()+
geom_sf(data = LATracts %>%
st_transform(crs=4326))+
geom_point(data = dat_census %>%
mutate(hour = hour(new_start_time),
weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"),
time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush"))%>%
group_by(start_station, start_lan, start_lon, weekend, time_of_day) %>%
tally(),
aes(x=start_lon, y = start_lan, color = n),
fill = "transparent", alpha = 0.5, size = 0.5)+
scale_colour_viridis(direction = -1,
discrete = FALSE, option = "D")+
ylim(min(dat_census$start_lan), max(dat_census$start_lan))+
xlim(min(dat_census$start_lon), max(dat_census$start_lon))+
facet_grid(weekend ~ time_of_day)+
labs(title="Bike share trips per hr by station. LA, May, 2022",
caption = "Figure 3-7")+
mapTheme

3.4.3 Space/time correlation
week20 <-
filter(ride.panel , week == 20 & dotw == "Mon")
week20.panel <-
expand.grid(
interval60 = unique(week20$interval60),
start_station = unique(ride.panel$start_station))
ride.animation.data <-
mutate(week20, Trip_Counter = 1) %>%
select(interval60, start_station, start_lon, start_lan, Trip_Counter) %>%
right_join(week20.panel) %>%
group_by(interval60, start_station, start_lon, start_lan,) %>%
summarize(Trip_Count = sum(Trip_Counter, na.rm=T)) %>%
ungroup() %>%
mutate(Trips = case_when(Trip_Count == 0 ~ "0 trips",
Trip_Count > 0 & Trip_Count <= 2 ~ "1-2 trips",
Trip_Count > 2 & Trip_Count <= 5 ~ "3-5 trips",
Trip_Count > 5 & Trip_Count <= 10 ~ "6-10 trips",
Trip_Count > 10 & Trip_Count <= 15 ~ "11-15 trips",
Trip_Count > 15 ~ "16+ trips")) %>%
mutate(Trips = fct_relevel(Trips, "0 trips","1-2 trips","3-5 trips",
"6-10 trips","6-10 trips","16+ trips"))
rideshare_animation <-
ggplot() +
geom_sf(data = LATracts %>%
st_transform(crs=4326)) +
geom_point(data = ride.animation.data,
aes(x = start_lon, y = start_lan, fill = Trips), size = 0.5, alpha = 1.5)+
scale_color_manual(values = palette5) +
labs(title = "Rideshare pickups for one day in May 2022, LA",
subtitle = "60 minute intervals: {current_frame}",
caption = "Figure 3-8") +
transition_manual(interval60) +
mapTheme
animate(rideshare_animation, duration=75, renderer = gifski_renderer())
rideshare_animation <-
ggplot() +
geom_sf(data = ride.animation.data, aes(fill = Trips)) +
scale_fill_manual(values = palette5) +
labs(title = "Rideshare pickups for one day in November 2018",
subtitle = "15 minute intervals: {current_frame}") +
transition_manual(interval15) +
mapTheme
4 Modelling
4.1 Building model
In this section, five different linear regressions are estimated on
ride.Train, each with different fixed effects: 1.
reg1 focuses on just time, including hour fixed effects,
day of the week, and Temperature. 2. reg2
focuses on just space effects with the start_station fixed
effects. 3. reg3 includes both time and space fixed
effects. 4. reg4 adds the time lag features.
5. reg5 adds the holiday lag features.
reg1 <-
lm(Trip_Count ~ hour(interval60) + dotw + Temperature, data=ride.Train)
reg2 <-
lm(Trip_Count ~ start_station + dotw + Temperature, data=ride.Train)
reg3 <-
lm(Trip_Count ~ start_station + hour(interval60) + dotw + Temperature + Precipitation,
data=ride.Train)
reg4 <-
lm(Trip_Count ~ start_station + hour(interval60) + dotw + Temperature + Precipitation +
lagHour + lag2Hours +lag3Hours + lag12Hours + lag1day,
data=ride.Train)
reg5 <-
lm(Trip_Count ~ start_station + hour(interval60) + dotw + Temperature + Precipitation +
lagHour + lag2Hours +lag3Hours +lag12Hours + lag1day + holiday, # + holidayLag ,
data=ride.Train)
4.2 Predict for test data
Models built above will be used for predicting data in
ride.Test which includes 2 sf tibbles (with
geometries).
ride.Test.weekNest <-
ride.Test %>%
nest(-week)
ride.Test.weekNest
## # A tibble: 2 × 2
## week data
## <dbl> <list>
## 1 21 <tibble [35,096 × 30]>
## 2 22 <tibble [34,026 × 30]>
Next, a function is created that takes a tibble, dat and
a regression model, fit as its inputs, and outputs
predictions as pred. Each week in
ride.Trest.weekNest can be predicted by this function.
model_pred <- function(dat, fit){
pred <- predict(fit, newdata = dat)}
The code below will loop through each model for each testing week and
mutate summary statistics, including the predictions, observed, absolute
error, MAE and SD AE.
week_predictions <-
ride.Test.weekNest %>%
mutate(ATime_FE = map(.x = data, fit = reg1, .f = model_pred),
BSpace_FE = map(.x = data, fit = reg2, .f = model_pred),
CTime_Space_FE = map(.x = data, fit = reg3, .f = model_pred),
DTime_Space_FE_timeLags = map(.x = data, fit = reg4, .f = model_pred),
ETime_Space_FE_timeLags_holidayLags = map(.x = data, fit = reg5, .f = model_pred)) %>%
gather(Regression, Prediction, -data, -week) %>%
mutate(Observed = map(data, pull, Trip_Count),
Absolute_Error = map2(Observed, Prediction, ~ abs(.x - .y)),
MAE = map_dbl(Absolute_Error, mean, na.rm = TRUE),
sd_AE = map_dbl(Absolute_Error, sd, na.rm = TRUE))
week_predictions
A 2 (weeks) times 5 (models) table is created above, each row
presents the statistics of one week in ride.Trest.weekNest
predicted by a regression model.
5 Examing accuracy
In this section, Mean Absolute Error(MAE) is calculated on
ride.Test for each model.
5.1 Examine Error Metrics for Accuracy
Figure 5-1 shows goodness of fit by week and model. The MAE for the
time effects model (reg1) in week 21 is comparable to the
mean observed Trip_Count of 0.256. With increasing
sophistication, the model becomes more accurate and more
generalizable.
week_predictions %>%
dplyr::select(week, Regression, MAE) %>%
gather(Variable, MAE, -Regression, -week) %>%
ggplot(aes(week, MAE)) +
geom_bar(aes(fill = Regression), position = "dodge", stat="identity") +
scale_fill_manual(values = palette5) +
labs(title = "Mean Absolute Errors by model specification and week",
caption = "Figure 5-1") +
plotTheme

For each model, predicted and observed Trip_Count is
taken out of the spatial context and their means of plotted in time
series form below. It seems that model A and C have the same time trend
because the spatial context has been removed.
The time lags facilitate the capacity to predict for the highest
peaks as sophistication increases.
week_predictions %>%
mutate(interval60 = map(data, pull, interval60),
start_station = map(data, pull, start_station)) %>%
dplyr::select(interval60, start_station, Observed, Prediction, Regression) %>%
unnest() %>%
gather(Variable, Value, -Regression, -interval60, -start_station) %>%
group_by(Regression, Variable, interval60) %>%
summarize(Value = sum(Value)) %>%
ggplot(aes(interval60, Value, colour=Variable)) +
geom_line(size = 1.1) +
facet_wrap(~Regression, ncol=1) +
labs(title = "Predicted/Observed bike share time series", subtitle = "LA; A test set of 2 weeks", x = "Hour", y= "Station Trips",
caption = "Figure 5-2") +
plotTheme

Figure 5-3 illustrates the observed and predicted distribution for
different times of day durig weekdays and weekends. It is clear that the
models are under predicting in general.
week_predictions %>%
mutate(interval60 = map(data, pull, interval60),
start_station = map(data, pull, start_station),
start_lan = map(data, pull, start_lan),
start_lon = map(data, pull, start_lon),
dotw = map(data, pull, dotw)) %>%
select(interval60, start_lan, start_lon,
start_station, Observed, Prediction, Regression,
dotw) %>%
unnest() %>%
filter(Regression == "ETime_Space_FE_timeLags_holidayLags")%>%
mutate(weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"),
time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush"))%>%
ggplot()+
geom_point(aes(x= Observed, y = Prediction))+
geom_smooth(aes(x= Observed, y= Prediction), method = "lm", se = FALSE, color = "red")+
geom_abline(slope = 1, intercept = 0)+
facet_grid(time_of_day~weekend)+
labs(title="Observed vs Predicted",
x="Observed trips",
y="Predicted trips",
caption = "Figure 5-3")+
plotTheme

5.2 Space-Time Error Evaluation
Figure 5-4 plots mean absolute errors on map by weekdays/weekends and
time of a day.
week_predictions %>%
mutate(interval60 = map(data, pull, interval60),
start_station = map(data, pull, start_station),
start_lan = map(data, pull, start_lan),
start_lon = map(data, pull, start_lon),
dotw = map(data, pull, dotw) ) %>%
select(interval60, start_station, start_lon,
start_lan, Observed, Prediction, Regression,
dotw) %>%
unnest() %>%
filter(Regression == "ETime_Space_FE_timeLags_holidayLags")%>%
mutate(weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"),
time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush")) %>%
group_by(start_station, weekend, time_of_day, start_lon, start_lan) %>%
summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
ggplot(.)+
geom_sf(data = LACensus, color = "grey", fill = "transparent")+
geom_point(aes(x = start_lon, y = start_lan, color = MAE),
fill = "transparent", size = 0.5, alpha = 0.4)+
scale_colour_viridis(direction = -1,
discrete = FALSE, option = "D")+
ylim(min(dat_census$start_lan), max(dat_census$start_lan))+
xlim(min(dat_census$start_lon), max(dat_census$start_lon))+
facet_grid(weekend~time_of_day)+
labs(title="Mean Absolute Errors, Test Set",
caption = "Figure 5-4")+
mapTheme

5.3 Socio-economic variables
One might think that the station locations probably relate to likely
users, who seem to be commuting downtown to the loop. Figure 5-4 plots
MAE by three socio-economic variables: median income, percent taking
public transportation and percent of white. Several stations show
resistance to the models - they have high income, low public
transportation usage and more than a half of white population.
week_predictions %>%
mutate(interval60 = map(data, pull, interval60),
start_station = map(data, pull, start_station),
start_lan = map(data, pull, start_lan),
start_lon = map(data, pull, start_lon),
dotw = map(data, pull, dotw),
Percent_Taking_Public_Trans = map(data, pull, Percent_Taking_Public_Trans),
Med_Inc = map(data, pull, Med_Inc),
Percent_White = map(data, pull, Percent_White)) %>%
select(interval60, start_station, start_lon,
start_lan, Observed, Prediction, Regression,
dotw, Percent_Taking_Public_Trans, Med_Inc, Percent_White) %>%
unnest() %>%
filter(Regression == "ETime_Space_FE_timeLags_holidayLags")%>%
mutate(weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"),
time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush")) %>%
filter(time_of_day == "AM Rush") %>%
group_by(start_station, Percent_Taking_Public_Trans, Med_Inc, Percent_White) %>%
summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
gather(-start_station, -MAE, key = "variable", value = "value")%>%
ggplot(.)+
#geom_sf(data = LACensus, color = "grey", fill = "transparent")+
geom_point(aes(x = value, y = MAE), alpha = 0.4)+
geom_smooth(aes(x = value, y = MAE), method = "lm", se= FALSE)+
facet_wrap(~variable, scales = "free")+
labs(title="Errors as a function of socio-economic variables",
y="Mean Absolute Error (Trips)",
caption = "Figure 5-4")+
plotTheme

6 Cross-Validation
Below, a parameter called fitControl is set to specify
the number of k-fold prtitions - in this case 100. In the code below,
set.seed ensures reproducible folds. An object
reg.cv, is estimated using the same regression as specified
in reg 4.
fitControl <- trainControl(method = "cv", number = 100)
set.seed(8888)
reg.cv <-
train(Trip_Count ~ start_station + hour(interval60) + dotw + Temperature + Precipitation +
lagHour + lag2Hours +lag3Hours + lag12Hours + lag1day,
data=ride.panel,
method = "lm", trControl = fitControl, na.action = na.pass)
reg.cv
## Linear Regression
##
## 172912 samples
## 10 predictor
##
## No pre-processing
## Resampling: Cross-Validated (100 fold)
## Summary of sample sizes: 171183, 171183, 171183, 171183, 171183, 171183, ...
## Resampling results:
##
## RMSE Rsquared MAE
## 0.497709 0.2339435 0.2295348
##
## Tuning parameter 'intercept' was held constant at a value of TRUE
The cross-validation output provides very important goodness of fit
information. The resample shows the first five outputs of
100 folds.
reg.cv$resample[1:5,]
## RMSE Rsquared MAE Resample
## 1 0.4378364 0.24580020 0.2218254 Fold001
## 2 0.4857485 0.34633998 0.2280337 Fold002
## 3 0.4930859 0.20575242 0.2330104 Fold003
## 4 0.4642082 0.17568579 0.2326678 Fold004
## 5 0.4936677 0.06945884 0.2304217 Fold005
MAE <- reg.cv$resample[,3]
df <- data.frame(MAE)
ggplot(df, aes(x=MAE)) +
geom_histogram(aes(y=..density..),bins=35, fill="#FB8C6F") +
labs(
x="Cross-Validation MAE Histogram",
y="MAE",
caption = "Figure 6-1"
)

plotTheme
LS0tCnRpdGxlOiAiQmlrZSBzaGFyZSIKYXV0aG9yOiAiWWluZ3h1ZSBPdSIKZGF0ZTogIjIwMjItMTEtMTEiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBjb2RlX2ZvbGRpbmc6ICJoaWRlIgogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCiMgMSBJbnRyb2R1Y3Rpb24KClRoZSByZXF1aXJlbWVudCB0byAicmUtYmFsYW5jZSIgYmljeWNsZXMgdGhyb3VnaG91dCB0aGUgbmV0d29yayBpcyBvbmUgb2YgdGhlIGRpZmZpY3VsdCBvcGVyYXRpb25hbCBpc3N1ZXMgZmFjZWQgYnkgdXJiYW4gYmlrZSBzaGFyZSBzeXN0ZW1zLiBJZiB0aGVyZSBhcmUgbm8gYmlrZXMgYXZhaWxhYmxlIGZvciBwaWNrdXAgYXQgYSBkb2NrIG9yIGF2YWlsYWJsZSBkb2NraW5nIHNwb3RzIHRvIGxlYXZlIGEgYmlrZSwgYmlrZSBzaGFyZSBpcyB1c2VsZXNzLiBSZS1iYWxhbmNpbmcgaXMgdGhlIGFjdGl2aXR5IG9mIHBoeXNpY2FsbHkgcmVkaXN0cmlidXRpbmcgYmlrZXMgdG8gZW5zdXJlIHRoYXQgYSBiaWtlIG9yIGEgZG9ja2luZyBsb2NhdGlvbiBpcyBhY2Nlc3NpYmxlIHdoZW4gbmVlZGVkIHdoaWxlIGFudGljaXBhdGluZyAob3IgcHJvamVjdGluZykgYmlrZSBzaGFyaW5nIGRlbWFuZCBmb3IgYWxsIGRvY2tzIGF0IGFsbCB0aW1lcy4KClJlYmFsYW5jaW5nIGNhbiBiZSBhY2hpZXZlZCBieSBtdWx0aXBsZSB3YXlzLCB0cmFuc3BvcnRpbmcgZXhjZXNzIGJpa2VzIHRvIHdoZXJlIHRoZXkgYXJlIG5lZWRlZCBieSB0cnVja3MgbWlnaHQgYmUgb25lIG9mIHRoZSBtb3N0IG9wZXJhdGlvbmFsIGFuZCBlZmZpY2llbnQgd2F5cy4gT2YgY291cnNlLCBpdCBpcyBhbHNvIGEgZmVhc2libGUgd2F5IHRvIGVuY291cmFnZSBjeWNsaXN0cyB0byBwYXJrIHRoZWlyIGJpY3ljbGVzIGluIGRlc2lnbmF0ZWQgbG9jYXRpb25zIHRocm91Z2ggYXBwcm9wcmlhdGUgaW5jZW50aXZlcy4gQWxsIGluIGFsbCwgYWxtb3N0IGFsbCByZWJhbGFuY2luZyBtZXRob2RzIGFyZSBoaWdobHkgZGVwZW5kZW50IG9uIGFjY3VyYXRlIGFuYWx5c2lzIGFuZCBwcmVkaWN0aW9uIG9mIHRoZSBzcGF0aWFsIGFuZCB0aW1lIGRpc3RyaWJ1dGlvbiBvZiBiaWtlcy4gSW4gdGhpcyBwcm9qZWN0LCB0aGUgTEEgTWV0cm8gQmlrZSBTaGFyZSB0cmlwIGRhdGEgKHNvdXJjZTpodHRwczovL2Jpa2VzaGFyZS5tZXRyby5uZXQvYWJvdXQvZGF0YS8pIGZvciAyMDIyIFEyIHdpbGwgYmUgdXNlZCB0byBidWlsZCBtb2RlbHMgYW5kIGZvcmVjYXN0IHNwYWNlL3RpbWUgZGVtYW5kIGZvciBiaWtlIHNoYXJlIHVzYWdlcy4gCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKCiMgMiBTZXQgdXAKCkZpcnN0IG9mIGFsbCwgc3RhcnQgd2l0aCB0aGUgcmVxdWlyZWQgbGlicmFyaWVzIGFuZCBmdW5jdGlvbnMuIAoKYGBge3Igc2V0dXBfMTIsIGNhY2hlPVRSVUUsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHNmKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeSh0aWdyaXMpCmxpYnJhcnkodGlkeWNlbnN1cykKbGlicmFyeSh2aXJpZGlzKQpsaWJyYXJ5KHJpZW0pCmxpYnJhcnkoZ3JpZEV4dHJhKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmxpYnJhcnkoUlNvY3JhdGEpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShnaWZza2kpCmxpYnJhcnkoZ2dhbmltYXRlKQpsaWJyYXJ5KGNhcmV0KQpwbG90VGhlbWUgPC0gdGhlbWUoCiAgcGxvdC50aXRsZSA9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT04KSwKICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoc2l6ZSA9IDYpLAogIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwKICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICMgU2V0IHRoZSBlbnRpcmUgY2hhcnQgcmVnaW9uIHRvIGJsYW5rCiAgcGFuZWwuYmFja2dyb3VuZD1lbGVtZW50X2JsYW5rKCksCiAgcGxvdC5iYWNrZ3JvdW5kPWVsZW1lbnRfYmxhbmsoKSwKICAjcGFuZWwuYm9yZGVyPWVsZW1lbnRfcmVjdChjb2xvdXI9IiNGMEYwRjAiKSwKICAjIEZvcm1hdCB0aGUgZ3JpZAogIHBhbmVsLmdyaWQubWFqb3I9ZWxlbWVudF9saW5lKGNvbG91cj0iI0QwRDBEMCIsc2l6ZT0uMiksCiAgYXhpcy50aWNrcz1lbGVtZW50X2JsYW5rKCkpCm1hcFRoZW1lIDwtIHRoZW1lKHBsb3QudGl0bGUgPWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgICAgICAgICAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgICAgICAgICAgICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoc2l6ZSA9IDYpLAogICAgICAgICAgICAgICAgICBheGlzLmxpbmU9ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgYXhpcy50aWNrcz1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgIHBhbmVsLmJhY2tncm91bmQ9ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICBwYW5lbC5ib3JkZXI9ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICBwYW5lbC5ncmlkLm1ham9yPWVsZW1lbnRfbGluZShjb2xvdXIgPSAndHJhbnNwYXJlbnQnKSwKICAgICAgICAgICAgICAgICAgcGFuZWwuZ3JpZC5taW5vcj1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgIGxlZ2VuZC5kaXJlY3Rpb24gPSAidmVydGljYWwiLCAKICAgICAgICAgICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IiwKICAgICAgICAgICAgICAgICAgcGxvdC5tYXJnaW4gPSBtYXJnaW4oMSwgMSwgMSwgMSwgJ2NtJyksCiAgICAgICAgICAgICAgICAgIGxlZ2VuZC5rZXkuaGVpZ2h0ID0gdW5pdCgxLCAiY20iKSwgbGVnZW5kLmtleS53aWR0aCA9IHVuaXQoMC4yLCAiY20iKSkKcGFsZXR0ZTUgPC0gYygiI0ZCOEM2RiIsIiM3MzYwN0QiLCIjQzFCOUFFIiwiI0ZEQzY2NCIsIiMzNTVDN0QiKQpwYWxldHRlNCA8LSBjKCIjRkI4QzZGIiwiIzczNjA3RCIsIiNDMUI5QUUiLCIjRkRDNjY0IikKcGFsZXR0ZTIgPC0gYygiI0ZCOEM2RiIsIiM3MzYwN0QiKQpgYGAKCmBgYHtyIGluc3RhbGxfY2Vuc3VzX0FQSV9rZXksIHdhcm5pbmcgPSBGQUxTRSwgaW5jbHVkZT1GQUxTRSwgZXZhbCA9IFRSVUV9CiMgSW5zdGFsbCBDZW5zdXMgQVBJIEtleQp0aWR5Y2Vuc3VzOjpjZW5zdXNfYXBpX2tleSgiYTEyNDQ5NjNjMjE5MTMyZjMyM2I2ZmVmNzVkMzBiYTRkOTM5ZDFjNSIsIG92ZXJ3cml0ZSA9IFRSVUUpCmBgYAoKIyMgMi4xIEltcG9ydCBiaWtlIHNoYXJlIGRhdGEKClRoZSBMQSBNZXRybyBCaWtlIFNoYXJlIHRyaXAgZGF0YSAoc291cmNlOmh0dHBzOi8vYmlrZXNoYXJlLm1ldHJvLm5ldC9hYm91dC9kYXRhLykgZm9yIDIwMjIgUTIgaXMgcmVhZCBpbi4KCmBgYHtyIHJlYWRfZGF0ICwgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UgLCB3YXJuaW5nPUZBTFNFfQpkYXQgPC0gc3RfcmVhZCgiLi9tZXRyby10cmlwcy0yMDIyLXEyLmNzdiIpIApzdGF0aW9uX25hbWUgPC0gc3RfcmVhZCgiLi9tZXRyby1iaWtlLXNoYXJlLXN0YXRpb25zLTIwMjItMTAtMDEuY3N2IikKCmNvbG5hbWVzKHN0YXRpb25fbmFtZSlbY29sbmFtZXMoc3RhdGlvbl9uYW1lKSA9PSAnU3RhdGlvbl9JRCddIDwtICJzdGFydF9zdGF0aW9uIgoKZGF0IDwtCiAgbWVyZ2UoZGF0LCBzdGF0aW9uX25hbWVbYygiU3RhdGlvbl9OYW1lIiwgInN0YXJ0X3N0YXRpb24iKV0sIGJ5ID0gInN0YXJ0X3N0YXRpb24iKQojICBzdF90cmFuc2Zvcm0oJ0VTUkk6MTAyMjg2JykKYGBgCgpBcyBiZWxvdywgYHBhcnNlX2RhdGVfdGltZWAgZnVuY3Rpb24gaXMgdXNlZCB0byBzdGFuZGFyZGl6ZSB0aGUgYHN0YXJ0X3RpbWVgIGZpZWxkICh3aGVuIGEgdHJpcCBkZXBhcnRlZCkgaW50byB0aGUgMSBob3VyIGFuZCAxNSBtaW51dGUgaW50ZXJ2YWxzIG5lZWRlZCBmb3IgbGF0ZXIgYW5hbHlzaXMuIEZ1bmN0aW9uIGB3ZWVrYCBhbmQgYHdkYXlgIGNvbnZlcnQgdGhlIGRhdGEvdGltZSBzdGFtcCBpbnRvIHdlZWsgb2YgdGhlIHllYXIgYW5kIGRheSBvZiB0aGUgd2VlaywgcmVzcGVjdGl2ZWx5LgoKYGBge3IgdGltZV9iaW5zIH0KZGF0MiA8LSBkYXQgJT4lCiAgbXV0YXRlKGludGVydmFsNjAgPSBmbG9vcl9kYXRlKG1keV9obShzdGFydF90aW1lKSwgdW5pdCA9ICJob3VyIiksCiAgICAgICAgIGludGVydmFsMTUgPSBmbG9vcl9kYXRlKG1keV9obShzdGFydF90aW1lKSwgdW5pdCA9ICIxNSBtaW5zIiksCiAgICAgICAgIHdlZWsgPSB3ZWVrKGludGVydmFsNjApLAogICAgICAgICBkb3R3ID0gd2RheShpbnRlcnZhbDYwLCBsYWJlbD1UUlVFKSkgJT4lCiAgbXV0YXRlKG5ld19zdGFydF90aW1lID0gc3RycHRpbWUoYXMuY2hhcmFjdGVyKHN0YXJ0X3RpbWUpLCAiJW0vJWQvJVkgJUg6JU0iKSwKICAgICAgICAgbmV3X2VuZF90aW1lID0gc3RycHRpbWUoYXMuY2hhcmFjdGVyKGVuZF90aW1lKSwgIiVtLyVkLyVZICVIOiVNIikpICU+JQogIGZpbHRlcih3ZWVrPjE3ICYgd2VlazwyMykKZ2xpbXBzZShkYXQyKQpgYGAKCiMjIDIuMiBJbXBvcnQgQ2Vuc3VzIEluZm8KClRoZSBjb2RlIGJsb2NrIGJlbG93IHB1bGxzIGNlbnN1cyBnZW9ncmFwaHkgYW5kIHNvbWUgdmFyaWFibGVzIGZyb20gdGhlIGB0aWR5Y2Vuc3VzYCBwYWNrYWdlLiBUaGVzZSB3aWxsIGJlIHVzZWQgdG8gZGVtb25zdHJhdGUgZ2VuZXJhbGl6YWJpbGl0eSBsYXRlciwgYnV0IHdvbid0IGJlIHVzZWQgYXMgaW5kZXBlbmRlbnQgdmFyaWFibGVzIGJlY2F1c2UgdGhleSBlbmQgdXAgYmVpbmcgcGVyZmVjdGx5IGNvbGluZWFyIHdpdGggdGhlIHN0YXRpb25zIGZpeGVkIGVmZmVjdHMuIFNwYXRpYWwgaW5mb3JtYXRpb24gd2lsbCBiZSBhZGRlZCB0byByaWRlcnNoYXJlIGRhdGEgYXMgb3JpZ2luIGFuZCBkZXN0aW5hdGlvbiBkYXRhLgoKYGBge3IgZ2V0X2NlbnN1cywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGU9VFJVRSwgcmVzdWx0cyA9ICdoaWRlJ30KTEFDZW5zdXMgPC0gCiAgZ2V0X2FjcyhnZW9ncmFwaHkgPSAidHJhY3QiLCAKICAgICAgICAgIHZhcmlhYmxlcyA9IGMoIkIwMTAwM18wMDEiLCAiQjE5MDEzXzAwMSIsIAogICAgICAgICAgICAgICAgICAgICAgICAiQjAyMDAxXzAwMiIsICJCMDgwMTNfMDAxIiwKICAgICAgICAgICAgICAgICAgICAgICAgIkIwODAxMl8wMDEiLCAiQjA4MzAxXzAwMSIsIAogICAgICAgICAgICAgICAgICAgICAgICAiQjA4MzAxXzAxMCIsICJCMDEwMDJfMDAxIiksIAogICAgICAgICAgeWVhciA9IDIwMjAsIAogICAgICAgICAgc3RhdGUgPSAiQ0EiLCAKICAgICAgICAgIGdlb21ldHJ5ID0gVFJVRSwgCiAgICAgICAgICBjb3VudHk9YygiTG9zIEFuZ2VsZXMiKSwKICAgICAgICAgIG91dHB1dCA9ICJ3aWRlIikgJT4lCiAgcmVuYW1lKFRvdGFsX1BvcCA9ICBCMDEwMDNfMDAxRSwKICAgICAgICAgTWVkX0luYyA9IEIxOTAxM18wMDFFLAogICAgICAgICBNZWRfQWdlID0gQjAxMDAyXzAwMUUsCiAgICAgICAgIFdoaXRlX1BvcCA9IEIwMjAwMV8wMDJFLAogICAgICAgICBUcmF2ZWxfVGltZSA9IEIwODAxM18wMDFFLAogICAgICAgICBOdW1fQ29tbXV0ZXJzID0gQjA4MDEyXzAwMUUsCiAgICAgICAgIE1lYW5zX29mX1RyYW5zcG9ydCA9IEIwODMwMV8wMDFFLAogICAgICAgICBUb3RhbF9QdWJsaWNfVHJhbnMgPSBCMDgzMDFfMDEwRSkgJT4lCiAgc2VsZWN0KFRvdGFsX1BvcCwgTWVkX0luYywgV2hpdGVfUG9wLCBUcmF2ZWxfVGltZSwKICAgICAgICAgTWVhbnNfb2ZfVHJhbnNwb3J0LCBUb3RhbF9QdWJsaWNfVHJhbnMsCiAgICAgICAgIE1lZF9BZ2UsCiAgICAgICAgIEdFT0lELCBnZW9tZXRyeSkgJT4lCiAgbXV0YXRlKFBlcmNlbnRfV2hpdGUgPSBXaGl0ZV9Qb3AgLyBUb3RhbF9Qb3AsCiAgICAgICAgIE1lYW5fQ29tbXV0ZV9UaW1lID0gVHJhdmVsX1RpbWUgLyBUb3RhbF9QdWJsaWNfVHJhbnMsCiAgICAgICAgIFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucyA9IFRvdGFsX1B1YmxpY19UcmFucyAvIE1lYW5zX29mX1RyYW5zcG9ydCkKYGBgCgpgYGB7ciBleHRyYWN0X2dlb21ldHJpZXMgfQpMQVRyYWN0cyA8LSAKICBMQUNlbnN1cyAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgZGlzdGluY3QoR0VPSUQsIC5rZWVwX2FsbCA9IFRSVUUpICU+JQogIHNlbGVjdChHRU9JRCwgZ2VvbWV0cnkpICU+JSAKICBzdF9zZgpgYGAKCmBgYHtyIGFkZF9jZW5zdXNfdHJhY3RzICwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CgpkYXQyJHN0YXJ0X2xvbltkYXQyJHN0YXJ0X2xvbj09JyddIDwtIE5BCmRhdDIkc3RhcnRfbGF0W2RhdDIkc3RhcnRfbGF0PT0nJ10gPC0gTkEKZGF0MiRlbmRfbG9uW2RhdDIkZW5kX2xvbj09JyddIDwtIE5BCmRhdDIkZW5kX2xhdFtkYXQyJGVuZF9sYXQ9PScnXSA8LSBOQQoKZGF0X2NlbnN1cyA8LSAgc3Rfam9pbihkYXQyICU+JSAKICAgICAgICAgIGZpbHRlcihpcy5uYShzdGFydF9sb24pID09IEZBTFNFICYKICAgICAgICAgICAgICAgICAgIGlzLm5hKHN0YXJ0X2xhdCkgPT0gRkFMU0UgJgogICAgICAgICAgICAgICAgICAgaXMubmEoZW5kX2xhdCkgPT0gRkFMU0UgJgogICAgICAgICAgICAgICAgICAgaXMubmEoZW5kX2xvbikgPT0gRkFMU0UgKSU+JQogICAgICAgICAgc3RfYXNfc2YoLiwgY29vcmRzID0gYygic3RhcnRfbG9uIiwgInN0YXJ0X2xhdCIpLCBjcnMgPSA0MzI2KSwKICAgICAgICBMQVRyYWN0cyAlPiUKICAgICAgICAgIHN0X3RyYW5zZm9ybShjcnM9NDMyNiksCiAgICAgICAgam9pbj1zdF9pbnRlcnNlY3RzLAogICAgICAgICAgICAgIGxlZnQgPSBUUlVFKSAlPiUKICByZW5hbWUoT3JpZ2luLlRyYWN0ID0gR0VPSUQpICU+JQogIG11dGF0ZShzdGFydF9sb24gPSB1bmxpc3QobWFwKGdlb21ldHJ5LCAxKSksCiAgICAgICAgIHN0YXJ0X2xhbiA9IHVubGlzdChtYXAoZ2VvbWV0cnksIDIpKSklPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgc2VsZWN0KC1nZW9tZXRyeSklPiUKICBzdF9hc19zZiguLCBjb29yZHMgPSBjKCJlbmRfbG9uIiwgImVuZF9sYXQiKSwgY3JzID0gNDMyNikgJT4lCiAgc3Rfam9pbiguLCBMQVRyYWN0cyAlPiUKICAgICAgICAgICAgc3RfdHJhbnNmb3JtKGNycz00MzI2KSwKICAgICAgICAgIGpvaW49c3RfaW50ZXJzZWN0cywKICAgICAgICAgIGxlZnQgPSBUUlVFKSAlPiUKICByZW5hbWUoRGVzdGluYXRpb24uVHJhY3QgPSBHRU9JRCkgICU+JQogIG11dGF0ZShlbmRfbG9uID0gdW5saXN0KG1hcChnZW9tZXRyeSwgMSkpLAogICAgICAgICBlbmRfbGF0ID0gdW5saXN0KG1hcChnZW9tZXRyeSwgMikpKSU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICBzZWxlY3QoLWdlb21ldHJ5KQpgYGAKCiMjIDIuMyBJbXBvcnQgV2VhdGhlciBEYXRhCgpJdCB3b3VsZCBiZSByZWFzb25hYmxlIHRvIGFudGljaXBhdGUgdGhhdCBiYWQgd2VhdGhlciBpbiB0aGUgV2luZHkgQ2l0eSB3b3VsZCBlbmNvdXJhZ2UgcmlkZSBzaGFyaW5nLiBIZXJlLCBJIGltcG9ydGVkIHdlYXRoZXIgZGF0YSBmcm9tIExvcyBBbmdlbGVzIERvd250b3duL1VTQyAoU0lEIGNvZGU6IENRVCwgc2VydmluZyBwZXJpb2QgZnJvbSAxOTk5IHRpbGwgbm93KSB1c2luZyBgcmllbV9tZWFzdXJlc2AgYmV0d2VlbiAyMDIyLTA1LTAxIGFuZCAyMDIyLTA1LTMxLgoKQmVsb3cgYSBgd2VhdGhlci5QYW5lbGAgaXMgZ2VuZXJhdGVkIHRvIHN1bW1hcml6ZSB0ZW1wZXJhdHVyZSwgcHJlY2lwaXRhdGlvbiBhbmQgd2luZCBzcGVlZCBmb3IgZXZlcnkgaG91ciBpbiBNYXkgMjAyMi4KCmBgYHtyIHdlYXRoZXJ9CndlYXRoZXIuUGFuZWwgPC0gCiAgcmllbV9tZWFzdXJlcyhzdGF0aW9uID0gIkNRVCIsIGRhdGVfc3RhcnQgPSAiMjAyMi0wNC0zMCIsIGRhdGVfZW5kID0gIjIwMjItMDYtMDMiKSAlPiUKICBkcGx5cjo6c2VsZWN0KHZhbGlkLCB0bXBmLCBwMDFpLCBza250KSU+JQogIHJlcGxhY2UoaXMubmEoLiksIDApICU+JQogICAgbXV0YXRlKGludGVydmFsNjAgPSB5bWRfaChzdWJzdHIodmFsaWQsMSwxMykpKSAlPiUKICAgIG11dGF0ZSh3ZWVrID0gd2VlayhpbnRlcnZhbDYwKSwKICAgICAgICAgICBkb3R3ID0gd2RheShpbnRlcnZhbDYwLCBsYWJlbD1UUlVFKSkgJT4lCiAgICBncm91cF9ieShpbnRlcnZhbDYwKSAlPiUKICAgIHN1bW1hcml6ZShUZW1wZXJhdHVyZSA9IG1heCh0bXBmKSwKICAgICAgICAgICAgICBQcmVjaXBpdGF0aW9uID0gc3VtKHAwMWkpLAogICAgICAgICAgICAgIFdpbmRfU3BlZWQgPSBtYXgoc2tudCkpICU+JQogICAgbXV0YXRlKFRlbXBlcmF0dXJlID0gaWZlbHNlKFRlbXBlcmF0dXJlID09IDAsIDQyLCBUZW1wZXJhdHVyZSkpCgpnbGltcHNlKHdlYXRoZXIuUGFuZWwpCmBgYAoKQmVsb3cgdGhlIHdlYXRoZXIgZGF0YSBpcyBwbG90dGVkIGFzIGEgdGltZSBzZXJpZXMgdXNpbmcgYGdyaWQuYXJyYW5nZWAuCgpgYGB7ciBwbG90X3dlYXRoZXIsIGNhdGNoZSA9IFRSVUV9CmdyaWQuYXJyYW5nZSgKICBnZ3Bsb3Qod2VhdGhlci5QYW5lbCwgYWVzKGludGVydmFsNjAsUHJlY2lwaXRhdGlvbikpICsgZ2VvbV9saW5lKCkgKyAKICBsYWJzKHRpdGxlPSJQZXJjaXBpdGF0aW9uIiwgeD0iSG91ciIsIHk9IlBlcmVjaXBpdGF0aW9uIikgKyBwbG90VGhlbWUsCiAgZ2dwbG90KHdlYXRoZXIuUGFuZWwsIGFlcyhpbnRlcnZhbDYwLFdpbmRfU3BlZWQpKSArIGdlb21fbGluZSgpICsgCiAgICBsYWJzKHRpdGxlPSJXaW5kIFNwZWVkIiwgeD0iSG91ciIsIHk9IldpbmQgU3BlZWQiKSArIHBsb3RUaGVtZSwKICBnZ3Bsb3Qod2VhdGhlci5QYW5lbCwgYWVzKGludGVydmFsNjAsVGVtcGVyYXR1cmUpKSArIGdlb21fbGluZSgpICsgCiAgICBsYWJzKHRpdGxlPSJUZW1wZXJhdHVyZSIsIHg9IkhvdXIiLCB5PSJUZW1wZXJhdHVyZSIpICsgcGxvdFRoZW1lLAogIHRvcD0iV2VhdGhlciBEYXRhIC0gTEEgQ1FUIC0gTWF5LCAyMDIyIikKYGBgCgojIDMgRGVzY3JpYmUgYW5kIEV4cGxvcmUgdGhlIERhdGEKCiMjIDMuMSBDcmVhdGUgU3BhY2UtVGltZSBQYW5lbAoKVG8gbWFrZSBzdXJlIHRoYXQgZWFjaCB1bmlxdWUgYW5kIGhvdXIvZGF5IGNvbWJvIGV4aXN0cyBpbiBvdXIgZGF0YSBzZXQsIGFuIGVtcHR5IGBzdHVkeS5wYW5lbGAgaXMgY3JlYXRlZCB3aGljaCBoYXMgZWFjaCB1bmlxdWUgc3BhY2UvdGltZSBvYnNlcnZhdGlvbnMuIE5vIG1hdHRlciBhbiBvYnNlcnZhdGlvbiB0b29rIHBsYWNlIG9yIG5vdCwgZWFjaCB0aW1lIHBlcmlvZCBpbiB0aGUgc3R1ZHkgaXMgcmVwcmVzZW50ZWQgYnkgYSByb3cgaW4gaXQuIAoKVGhlIG1heGltdW0gbnVtYmVyIG9mIGNvbWJpbmF0aW9ucyBjYW4gYmUgY2FsY3VsYXRlZCBieSAyNCBob3VycyBwZXIgZGF5ICogNyBkYXlzIHBlciB3ZWVrICogNSB3ZWVrcyAqIHRoZSBudW1iZXIgb2Ygc3RhdGlvbnMuIFRoZW4sIGNvbXBhcmUgdGhhdCB0byB0aGUgbnVtYmVyIG9mIHJvd3MgaW4gYHN0dWR5LnBhbmVsYCBieSBgbnJvd2AuIEJvdGggc3RlcHMgc2hvdyB0aGUgc2FtZSByZXN1bHQ6IDE3NDUyOCwgd2hpY2ggbWVhbnMgdGhhdCB0aGUgY291bnRzIGlzIGNvcnJlY3QuCgpDb250aW51ZSB0byBqb2luIHRoZSBzdGF0aW9uIG5hbWUsIHRyYWN0IGFuZCBsYXRpdHVkZS9sb25naXR1ZGUuCgpgYGB7ciBwYW5lbF9sZW5ndGhfY2hlY2sgLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KbGVuZ3RoKHVuaXF1ZShkYXRfY2Vuc3VzJGludGVydmFsNjApKSAqIGxlbmd0aCh1bmlxdWUoZGF0X2NlbnN1cyRzdGFydF9zdGF0aW9uKSkKCnN0dWR5LnBhbmVsIDwtIAogIGV4cGFuZC5ncmlkKGludGVydmFsNjA9dW5pcXVlKGRhdF9jZW5zdXMkaW50ZXJ2YWw2MCksIAogICAgICAgICAgICAgIHN0YXJ0X3N0YXRpb24gPSB1bmlxdWUoZGF0X2NlbnN1cyRzdGFydF9zdGF0aW9uKSkgJT4lCiAgbGVmdF9qb2luKC4sIGRhdF9jZW5zdXMgJT4lCiAgICAgICAgICAgICAgc2VsZWN0KHN0YXJ0X3N0YXRpb24sIFN0YXRpb25fTmFtZSwgT3JpZ2luLlRyYWN0LCBzdGFydF9sb24sIHN0YXJ0X2xhbiApJT4lIAogICAgICAgICAgICAgIGRpc3RpbmN0KCkgJT4lCiAgICAgICAgICAgICAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbikgJT4lCiAgICAgICAgICAgICAgc2xpY2UoMSkpCm5yb3coc3R1ZHkucGFuZWwpICAgICAgCmBgYAoKSW4gdGhlIGNvZGUgYmVsb3csIGEgZnVsbCBwYW5lbCBpcyBzdW1tYXJpemluZyBieSBzdGF0aW9uIGZvciBlYWNoIHRpbWUgaW50ZXJ2YWwsIGtlZXAgY2Fuc3VzIGluZm8gYW5kIGxhdGl0dWRlL2xvbmdpdHVkZSBpbmZvcm1hdGlvbiBhbG9uZyBmb3Igam9pbmluZyBsYXRlciB0byBvdGhlciBkYXRhLiBTdGFydCBzdGF0aW9uIElEcyBhcmUgcmVtb3ZlZCBieSBgRkFMU0VgLgoKYGBge3IgY3JlYXRlX3BhbmVsICwgbWVzc2FnZSA9IEZBTFNFfQpyaWRlLnBhbmVsIDwtIAogIGRhdF9jZW5zdXMgJT4lCiAgbXV0YXRlKFRyaXBfQ291bnRlciA9IDEpICU+JQogIHJpZ2h0X2pvaW4oc3R1ZHkucGFuZWwpICU+JSAKICBncm91cF9ieShpbnRlcnZhbDYwLCBzdGFydF9zdGF0aW9uLCBTdGF0aW9uX05hbWUsIE9yaWdpbi5UcmFjdCwgc3RhcnRfbG9uLCBzdGFydF9sYW4pICU+JSAjIG1pc3NpbmcgImZyb21fc3RhdGlvbl9uYW1lIgogIHN1bW1hcml6ZShUcmlwX0NvdW50ID0gc3VtKFRyaXBfQ291bnRlciwgbmEucm09VCkpICU+JQogIGxlZnRfam9pbih3ZWF0aGVyLlBhbmVsKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgZmlsdGVyKGlzLm5hKHN0YXJ0X3N0YXRpb24pID09IEZBTFNFKSAlPiUKICBtdXRhdGUod2VlayA9IHdlZWsoaW50ZXJ2YWw2MCksCiAgICAgICAgIGRvdHcgPSB3ZGF5KGludGVydmFsNjAsIGxhYmVsID0gVFJVRSkpICU+JQogIGZpbHRlcihpcy5uYShPcmlnaW4uVHJhY3QpID09IEZBTFNFKQpgYGAKCmBgYHtyIGNlbnN1c19hbmRfcGFuZWwgLCBtZXNzYWdlID0gRkFMU0V9CnJpZGUucGFuZWwgPC0gCiAgbGVmdF9qb2luKHJpZGUucGFuZWwsIExBQ2Vuc3VzICU+JQogICAgICAgICAgICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUKICAgICAgICAgICAgICBzZWxlY3QoLWdlb21ldHJ5KSwgYnkgPSBjKCJPcmlnaW4uVHJhY3QiID0gIkdFT0lEIikpCmBgYAoKIyMgMy4yIENyZWF0ZSB0aW1lIGxhZ3MKCmBgYHtyIHRpbWVfbGFncyAsIG1lc3NhZ2UgPSBGQUxTRX0KcmlkZS5wYW5lbCA8LSAKICByaWRlLnBhbmVsICU+JSAKICBhcnJhbmdlKHN0YXJ0X3N0YXRpb24sIGludGVydmFsNjApICU+JSAKICBtdXRhdGUobGFnSG91ciA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwxKSwKICAgICAgICAgbGFnMkhvdXJzID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LDIpLAogICAgICAgICBsYWczSG91cnMgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMyksCiAgICAgICAgIGxhZzRIb3VycyA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCw0KSwKICAgICAgICAgbGFnMTJIb3VycyA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwxMiksCiAgICAgICAgIGxhZzFkYXkgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMjQpLAogICAgICAgICBob2xpZGF5ID0gaWZlbHNlKHlkYXkoaW50ZXJ2YWw2MCkgPT0gMTQ4LDEsMCkpICU+JQogICBtdXRhdGUoZGF5ID0geWRheShpbnRlcnZhbDYwKSkgJT4lCiAgIG11dGF0ZShob2xpZGF5TGFnID0gY2FzZV93aGVuKGRwbHlyOjpsYWcoaG9saWRheSwgMSkgPT0gMSB+ICJQbHVzT25lRGF5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OmxhZyhob2xpZGF5LCAyKSA9PSAxIH4gIlBsdXN0VHdvRGF5cyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpsYWcoaG9saWRheSwgMykgPT0gMSB+ICJQbHVzdFRocmVlRGF5cyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpsZWFkKGhvbGlkYXksIDEpID09IDEgfiAiTWludXNPbmVEYXkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6bGVhZChob2xpZGF5LCAyKSA9PSAxIH4gIk1pbnVzVHdvRGF5cyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpsZWFkKGhvbGlkYXksIDMpID09IDEgfiAiTWludXNUaHJlZURheXMiKSwKICAgICAgICAgaG9saWRheUxhZyA9IGlmZWxzZShpcy5uYShob2xpZGF5TGFnKSA9PSBUUlVFLCAwLCBob2xpZGF5TGFnKSkKYGBgCgpgYGB7ciBldmFsdWF0ZV9sYWdzICwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CmFzLmRhdGEuZnJhbWUocmlkZS5wYW5lbCkgJT4lCiAgICBncm91cF9ieShpbnRlcnZhbDYwKSAlPiUgCiAgICBzdW1tYXJpc2VfYXQodmFycyhzdGFydHNfd2l0aCgibGFnIiksICJUcmlwX0NvdW50IiksIG1lYW4sIG5hLnJtID0gVFJVRSkgJT4lCiAgICBnYXRoZXIoVmFyaWFibGUsIFZhbHVlLCAtaW50ZXJ2YWw2MCwgLVRyaXBfQ291bnQpICU+JQogICAgbXV0YXRlKFZhcmlhYmxlID0gZmFjdG9yKFZhcmlhYmxlLCBsZXZlbHM9YygibGFnSG91ciIsImxhZzJIb3VycyIsImxhZzNIb3VycyIsImxhZzRIb3VycyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJsYWcxMkhvdXJzIiwibGFnMWRheSIpKSklPiUKICAgIGdyb3VwX2J5KFZhcmlhYmxlKSAlPiUgIAogICAgc3VtbWFyaXplKGNvcnJlbGF0aW9uID0gcm91bmQoY29yKFZhbHVlLCBUcmlwX0NvdW50KSwyKSkKYGBgCgojIyAzLjMgVHJhbmluZy90ZXN0aW5nIGRhdGEgc2V0CgpIZXJlLCBhIHRpbWUgc2VyaWVzIGFwcHJvYWNoIGlzIHRha2VuLCBhIHRyYWluaW5nIGRhdGEgc2V0IHdpdGggMyB3ZWVrcyBvZiBkYXRhICh3ZWVrcyAxOC0yMCkgYW5kIGEgdGVzdGluZyBkYXRhIHNldCB3aXRoIDIgd2Vla3Mgb2YgZGF0YSAod2Vla3MgMjEtMjIpIGFyZSBjcmVhdGVkLiBDb2RlIGJlbG93IHNwbGl0IHRoZSBkYXRhIGJ5IGB3ZWVrYC4KCmBgYHtyIHRyYWluX3Rlc3QgfQpyaWRlLlRyYWluIDwtIGZpbHRlcihyaWRlLnBhbmVsLCB3ZWVrID49IDE4ICYgd2VlayA8PSAyMCkKcmlkZS5UZXN0IDwtIGZpbHRlcihyaWRlLnBhbmVsLCB3ZWVrID49MjEgJiB3ZWVrIDw9IDIyKQpgYGAKCiMjIDMuNCBFeHBsb3JhdG9yaW5nIGRhdGEKCkluIHRoaXMgc2VjdGlvbiwgdGhlIHJpZGVyIHNoYXJlIGRhdGEgaXMgZXhwbG9yZWQgZm9yIHRpbWUsIHNwYWNlLCB3ZWF0aGVyLCBhbmQgZGVtb2dyYXBoaWMgcmVsYXRpb25zaGlwcy4gSWYgdGhlIGBUcmlwX0NvdW50YCBleGhpYml0IHNlcmlhbCh0ZW1wb3JhbCkgY29ycmVsYXRpb24sIHRoZSB0aW1lIGxhZyBmZWF0dXJlcyB3aWxsIGxlYWQgdG8gYmV0dGVyIHByZWRpY3Rpb25zLgoKIyMjIDMuNC4xIFNlcmlhbCBhdXRvY29ycmVsYXRpb24KCldlIGJlZ2luIGJ5IGV4YW1pbmluZyB0aGUgdGltZSBhbmQgZnJlcXVlbmN5IGNvbXBvbmVudHMgb2Ygb3VyIGRhdGEuCgpGaXJzdCwgd2UgbG9vayBhdCB0aGUgb3ZlcmFsbCB0aW1lIHBhdHRlcm4gLSB0aGVyZSBpcyBjbGVhcmx5IGEgZGFpbHkgcGVyaW9kaWNpdHkgYW5kIHRoZXJlIGFyZSBsdWxsIHBlcmlvZHMgb24gd2Vla2VuZHMuIGBnZW8tdmxpbmVgIGlzIHVzZWQgdG8gdmlzdWFsaXplIGBNb25kYXlzYC4gQWxsIG9mIHRoZSBmaXZlIHdlZWtzIGV4aGliaXQgc2ltaWxhciB0aW1lIHNlcmllcyBwYXR0ZXJucyB3aXRoIGNvbnNpc3RlbnQgcGVha3MgYW5kIHRyb3VnaHMuIFdoaWNoIHN1Z2dlc3RzIHRoZSBwcmVzZW5jZSBvZiBzZXJpYWwgY29ycmVsYXRpb24uCgpgYGB7ciB0cmlwX3RpbWVzZXJpZXMscmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UgLCB3YXJuaW5nPUZBTFNFfQptb25kYXlzIDwtIAogIG11dGF0ZShyaWRlLnBhbmVsLAogICAgICAgICBtb25kYXkgPSBpZmVsc2UoZG90dyA9PSAiTW9uIiAmIGhvdXIoaW50ZXJ2YWw2MCkgPT0gMSwKICAgICAgICAgICAgICAgICAgICAgICAgIGludGVydmFsNjAsIDApKSAlPiUKICBmaWx0ZXIobW9uZGF5ICE9IDApIAoKc3RfZHJvcF9nZW9tZXRyeShyYmluZCgKICBtdXRhdGUocmlkZS5UcmFpbiwgTGVnZW5kID0gIlRyYWluaW5nIiksIAogIG11dGF0ZShyaWRlLlRlc3QsIExlZ2VuZCA9ICJUZXN0aW5nIikpKSAlPiUKICAgIGdyb3VwX2J5KExlZ2VuZCwgaW50ZXJ2YWw2MCkgJT4lIAogICAgICBzdW1tYXJpemUoVHJpcF9Db3VudCA9IHN1bShUcmlwX0NvdW50KSkgJT4lCiAgICAgIHVuZ3JvdXAoKSAlPiUgCiAgICAgIGdncGxvdChhZXMoaW50ZXJ2YWw2MCwgVHJpcF9Db3VudCwgY29sb3VyID0gTGVnZW5kKSkgKyBnZW9tX2xpbmUoKSArCiAgICAgICAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlMikgKwogICAgICAgIGdlb21fdmxpbmUoZGF0YSA9IG1vbmRheXMsIGFlcyh4aW50ZXJjZXB0ID0gbW9uZGF5KSkgKwogICAgICAgIGxhYnModGl0bGU9IkxBIFJpZGVzaGFyZSB0cmlwcyBieSB3ZWVrOiBBcHIgMzAgLSBKdW4gMDMsIDIwMjIiLAogICAgICAgICAgICAgc3VidGl0bGU9IkRvdHRlZCBsaW5lcyBmb3IgTW9uZGF5cyIsIAogICAgICAgICAgICAgeD0iRGF5IiwgeT0iVHJpcCBDb3VudCIsCiAgICAgICAgICAgICBjYXB0aW9uID0gIkZpZ3VyZSAzLTEiKSArCiAgICAgICAgcGxvdFRoZW1lICsgdGhlbWUocGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSkgICAgCmBgYAoKTmV4dCwgdGhlIHRpbWUgYGxhZ2AgZmVhdHVyZXMgYXJlIHRlc3RlZCBmb3IgY29ycmVsYXRpb24gd2l0aCBgVHJpcF9Db3VudGAuIGBwbG90RGF0YS5sYWdgIHJldHVybnMgdGhlIGBUcmlwX0NvdW50YCBhbmQgdGltZSBsYWcgZmVhdHVyZXMgZm9yIHdlZWsgMTguIGBmY3RfcmVsZXZlbGAgcmVvcmRlcnMgdGhlIGxhZyBsZXZlbHMuIE9taXQgdGhhdCBsaW5lIGFuZCB0cnkgYGxldmVscyhwbG90RGF0YS5sYWckVmFyaWFibGUpYC4KClBlYXJzb24gY29ycmVsYXRpb24gaXMgdGhlbiBjYWxjdWxhdGVkIGZvciBlYWNoIGBWYXJpYWJsZWAgaW4gYGNvcnJlbGF0aW9uLmxhZ2AuCgpXZWFrIGBUcmlwX0NvdW50YCBjb3JyZWxhdGlvbnMgYXJlIHZpc3VhbGl6ZWQgYmVsb3cuCgpgYGB7ciB0aW1lX2xhZywgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UgLCB3YXJuaW5nPUZBTFNFfQoKcGxvdERhdGEubGFnIDwtCiAgZmlsdGVyKGFzLmRhdGEuZnJhbWUocmlkZS5wYW5lbCksIHdlZWsgPT0gMTgpICU+JQogIGRwbHlyOjpzZWxlY3Qoc3RhcnRzX3dpdGgoImxhZyIpLCBUcmlwX0NvdW50KSAlPiUKICBnYXRoZXIoVmFyaWFibGUsIFZhbHVlLCAtVHJpcF9Db3VudCkgJT4lCiAgbXV0YXRlKFZhcmlhYmxlID0gZmN0X3JlbGV2ZWwoVmFyaWFibGUsICJsYWdIb3VyIiwibGFnMkhvdXJzIiwibGFnM0hvdXJzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImxhZzRIb3VycyIsImxhZzEySG91cnMiLCJsYWcxZGF5IikpCmNvcnJlbGF0aW9uLmxhZyA8LQogIGdyb3VwX2J5KHBsb3REYXRhLmxhZywgVmFyaWFibGUpICU+JQogICAgc3VtbWFyaXplKGNvcnJlbGF0aW9uID0gcm91bmQoY29yKFZhbHVlLCBUcmlwX0NvdW50LCB1c2UgPSAiY29tcGxldGUub2JzIiksIDIpKSAKCnBsb3REYXRhLmxhZyAlPiUKZ2dwbG90KCkrCiAgZ2VvbV9wb2ludChhZXMoeD0gVmFsdWUsIHkgPSBUcmlwX0NvdW50KSkrCiAgZ2VvbV90ZXh0KGRhdGEgPSBjb3JyZWxhdGlvbi5sYWcsIGFlcyhsYWJlbCA9IHBhc3RlKCJyID0iLCByb3VuZChjb3JyZWxhdGlvbiwgMikpKSwKICAgICAgICAgICAgeD0tSW5mLCB5PUluZiwgdmp1c3QgPSAxLjUsIGhqdXN0ID0gLS4xKSArCiAgZ2VvbV9zbW9vdGgoYWVzKHg9IFZhbHVlLCB5PSBUcmlwX0NvdW50KSwgbWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgY29sb3IgPSAiNzM2MDdEIikrCiAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQgPSAwKSsKICBmYWNldF93cmFwKH5WYXJpYWJsZSwgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlIikgKwogIGxhYnModGl0bGU9IlJpZGVyc2hhcmUgdHJpcCBjb3VudCBhcyBhIGZ1bmN0aW9uIG9mIHRpbWUgbGFncyIsCiAgICAgICB4PSJMYWcgVHJpcCBDb3VudCIsIAogICAgICAgeT0iVHJpcF9Db3VudCIsCiAgICAgICBjYXB0aW9uID0gIkZpZ3VyZSAzLTIiKSsKICBwbG90VGhlbWUKYGBgCgpGaWd1cmUgMy0zIGlsbHVzdHJhdGVzIHRoZSBkaXN0cmlidXRpb24gb2YgdHJpcCB2b2x1bWUgYnkgc3RhdGlvbiBmb3IgZGlmZmVyZW50IHRpbWVzIG9mIHRoZSBkYXkuIFRoZXJlIGFyZSBzb21lIHN0YXRpb25zIHdpdGggaGlnaCB2b2x1bWUgcGVyaW9kcywgYnV0IG1vcmUgaGF2ZSBsb3cgdm9sdW1lLiBUaGVyZWZvcmUsIG91ciBkYXRhIHdpbGwgY29uc2lzdCBvZiBhIG51bWJlciBvZiBsb3cgZGVtYW5kIHN0YXRpb24vaG91cnMgYW5kIGEgZmV3IGhpZ2ggZGVtYW5kIHN0YXRpb24gaG91cnMuCgpXZSBjYW4gYWxzbyB0cmFjayB0aGUgZGFpbHkgdHJlbmRzIGluIHJpZGVyc2hpcCBieSBkYXkgb2YgdGhlIHdlZWsgYW5kIHdlZWtlbmQgdmVyc3VzIHdlZWtkYXksIHRvIHNlZSB3aGF0IHRlbXBvcmFsIHBhdHRlcm5zIHdlJ2QgbGlrZSB0byBjb250cm9sIGZvci4KCmBgYHtyIG1lYW5fdHJpcHNfaGlzdCwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UgfQpkYXRfY2Vuc3VzICU+JQogICAgICAgIG11dGF0ZSh0aW1lX29mX2RheSA9IGNhc2Vfd2hlbihob3VyKGludGVydmFsNjApIDwgNyB8IGhvdXIoaW50ZXJ2YWw2MCkgPiAxOCB+ICJPdmVybmlnaHQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDcgJiBob3VyKGludGVydmFsNjApIDwgMTAgfiAiQU0gUnVzaCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTAgJiBob3VyKGludGVydmFsNjApIDwgMTUgfiAiTWlkLURheSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTUgJiBob3VyKGludGVydmFsNjApIDw9IDE4IH4gIlBNIFJ1c2giKSklPiUKICAgICAgICAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbiwgdGltZV9vZl9kYXkpICU+JQogICAgICAgICB0YWxseSgpJT4lCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbiwgdGltZV9vZl9kYXkpJT4lCiAgc3VtbWFyaXplKG1lYW5fdHJpcHMgPSBtZWFuKG4pKSU+JQogIGdncGxvdCgpKwogIGdlb21faGlzdG9ncmFtKGFlcyhtZWFuX3RyaXBzKSwgYmlud2lkdGggPSAxKSsKICBsYWJzKHRpdGxlPSJNZWFuIE51bWJlciBvZiBIb3VybHkgVHJpcHMgUGVyIFN0YXRpb24uIExBLCBBcHIgMzAgLSBKdW4gMDMsIDIwMjIiLAogICAgICAgeD0iTnVtYmVyIG9mIHRyaXBzIiwgCiAgICAgICB5PSJGcmVxdWVuY3kiLAogICAgICAgY2FwdGlvbiA9ICJGaWd1cmUgMy0zIikrCiAgZmFjZXRfd3JhcCh+dGltZV9vZl9kYXkpKwogIHBsb3RUaGVtZQpgYGAKCmBgYHtyIHRyaXBzX3N0YXRpb25fZG90dyB9CmdncGxvdChkYXRfY2Vuc3VzICU+JQogICAgICAgICBncm91cF9ieShpbnRlcnZhbDYwLCBzdGFydF9zdGF0aW9uKSAlPiUKICAgICAgICAgdGFsbHkoKSkrCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKG4pLCBiaW53aWR0aCA9IDUpKwogIGxhYnModGl0bGU9IkJpa2Ugc2hhcmUgdHJpcHMgcGVyIGhyIGJ5IHN0YXRpb24uIExBLCBBcHIgMzAgLSBKdW4gMDMsIDIwMjIiLAogICAgICAgeD0iVHJpcCBDb3VudHMiLCAKICAgICAgIHk9Ik51bWJlciBvZiBTdGF0aW9ucyIsCiAgICAgICBjYXB0aW9uID0gIkZpZ3VyZSAzLTQiKSsKICBwbG90VGhlbWUKYGBgCgpGaWd1cmUgMy01IGFuZCAzLTYgaWxsdXN0cmF0ZSB0b3RhbCBgVHJpcF9Db3VudHNgIGRpc3RyaWJ1dGlvbiBpbiBvbmUgZGF5IGJ5IHdlZWtkYXlzIGFuZCAgd2Vla2VuZHMuIEl0IGNsZWFyIHRoYXQgdGhlIHRyaXAgdm9sdW1lIG9mIGVhY2ggZGF5IGluIGEgd2VlayBoYXZlIHNpbWlsYXIgcGF0dGVybnM6IHJlYWNoaW5nIHBlYWsgYXQgYWJvdXQgNDAwIGluIDEyOjAwLTE3OjAwIGFuZCBmYWxsaW5nIGF0IG5lYXIgMCB2YWx1ZSBpbiAzOjAwIG9yIDI0OjAwLiBXZWVrZGF5cyBoYXZlIGFsbW9zdCBkb3VibGUgcGVhayB2b2x1bWUgdGhhbiB3ZWVrZW5kcy4KCmBgYHtyIHRyaXBzX2hvdXJfZG90dyB9CmdncGxvdChkYXRfY2Vuc3VzICU+JSBtdXRhdGUoaG91ciA9IGhvdXIobmV3X3N0YXJ0X3RpbWUpKSkrCiAgICAgZ2VvbV9mcmVxcG9seShhZXMoaG91ciwgY29sb3IgPSBkb3R3KSwgYmlud2lkdGggPSAxKSsKICBsYWJzKHRpdGxlPSJCaWtlIHNoYXJlIHRyaXBzIGluIExBLCBieSBkYXkgb2YgdGhlIHdlZWssIE1heSwgMjAyMiIsCiAgICAgICB4PSJIb3VyIiwgCiAgICAgICB5PSJUcmlwIENvdW50cyIsCiAgICAgICBjYXB0aW9uID0gIkZpZ3VyZSAzLTUiKSsKICAgICBwbG90VGhlbWUKZ2dwbG90KGRhdF9jZW5zdXMgJT4lIAogICAgICAgICBtdXRhdGUoaG91ciA9IGhvdXIobmV3X3N0YXJ0X3RpbWUpLAogICAgICAgICAgICAgICAgd2Vla2VuZCA9IGlmZWxzZShkb3R3ICVpbiUgYygiU3VuIiwgIlNhdCIpLCAiV2Vla2VuZCIsICJXZWVrZGF5IikpKSsKICAgICBnZW9tX2ZyZXFwb2x5KGFlcyhob3VyLCBjb2xvciA9IHdlZWtlbmQpLCBiaW53aWR0aCA9IDEpKwogIGxhYnModGl0bGU9IkJpa2Ugc2hhcmUgdHJpcHMgaW4gTEEgLSB3ZWVrZW5kIHZzIHdlZWtkYXksIEFwciAzMCAtIEp1biAwMywgMjAyMiIsCiAgICAgICB4PSJIb3VyIiwgCiAgICAgICB5PSJUcmlwIENvdW50cyIsCiAgICAgICBjYXB0aW9uID0gIkZpZ3VyZSAzLTYiKSsKICAgICBwbG90VGhlbWUKYGBgCgojIyMgMy40LjIgU3BhdGlhbCBhdXRvY29ycmVsYXRpb24KCkluIHRoaXMgcGFydCwgdGhlIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uIHdpbGwgYmUgZXhhbWluZWQuIEZpZ3VyZSAzLTcgc2hvd3MgdGhlIGBUcmlwLUNvdW50YCBwZXIgaG91ciBieSBzdGF0aW9uIGluIHdlZWtkYXlzIGFuZCB3ZWVrZW5kcywgcmVzcGVjdGl2ZWx5LgoKYGBge3Igb3JpZ2luX21hcCB9CmdncGxvdCgpKwogIGdlb21fc2YoZGF0YSA9IExBVHJhY3RzICU+JQogICAgICAgICAgc3RfdHJhbnNmb3JtKGNycz00MzI2KSkrCiAgZ2VvbV9wb2ludChkYXRhID0gZGF0X2NlbnN1cyAlPiUgCiAgICAgICAgICAgIG11dGF0ZShob3VyID0gaG91cihuZXdfc3RhcnRfdGltZSksCiAgICAgICAgICAgICAgICB3ZWVrZW5kID0gaWZlbHNlKGRvdHcgJWluJSBjKCJTdW4iLCAiU2F0IiksICJXZWVrZW5kIiwgIldlZWtkYXkiKSwKICAgICAgICAgICAgICAgIHRpbWVfb2ZfZGF5ID0gY2FzZV93aGVuKGhvdXIoaW50ZXJ2YWw2MCkgPCA3IHwgaG91cihpbnRlcnZhbDYwKSA+IDE4IH4gIk92ZXJuaWdodCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gNyAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxMCB+ICJBTSBSdXNoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxMCAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxNSB+ICJNaWQtRGF5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxNSAmIGhvdXIoaW50ZXJ2YWw2MCkgPD0gMTggfiAiUE0gUnVzaCIpKSU+JQogICAgICAgICAgICAgIGdyb3VwX2J5KHN0YXJ0X3N0YXRpb24sIHN0YXJ0X2xhbiwgc3RhcnRfbG9uLCB3ZWVrZW5kLCB0aW1lX29mX2RheSkgJT4lCiAgICAgICAgICAgICAgdGFsbHkoKSwKICAgICAgICAgICAgYWVzKHg9c3RhcnRfbG9uLCB5ID0gc3RhcnRfbGFuLCBjb2xvciA9IG4pLCAKICAgICAgICAgICAgZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGFscGhhID0gMC41LCBzaXplID0gMC41KSsKICBzY2FsZV9jb2xvdXJfdmlyaWRpcyhkaXJlY3Rpb24gPSAtMSwKICBkaXNjcmV0ZSA9IEZBTFNFLCBvcHRpb24gPSAiRCIpKwogIHlsaW0obWluKGRhdF9jZW5zdXMkc3RhcnRfbGFuKSwgbWF4KGRhdF9jZW5zdXMkc3RhcnRfbGFuKSkrCiAgeGxpbShtaW4oZGF0X2NlbnN1cyRzdGFydF9sb24pLCBtYXgoZGF0X2NlbnN1cyRzdGFydF9sb24pKSsKICBmYWNldF9ncmlkKHdlZWtlbmQgfiB0aW1lX29mX2RheSkrCiAgbGFicyh0aXRsZT0iQmlrZSBzaGFyZSB0cmlwcyBwZXIgaHIgYnkgc3RhdGlvbi4gTEEsIE1heSwgMjAyMiIsCiAgICAgICBjYXB0aW9uID0gIkZpZ3VyZSAzLTciKSsKICBtYXBUaGVtZQpgYGAKCiMjIyAzLjQuMyBTcGFjZS90aW1lIGNvcnJlbGF0aW9uCgpgYGB7ciB3ZWVrX3BhbmVsfQp3ZWVrMjAgPC0KICBmaWx0ZXIocmlkZS5wYW5lbCAsIHdlZWsgPT0gMjAgJiBkb3R3ID09ICJNb24iKQoKd2VlazIwLnBhbmVsIDwtCiAgZXhwYW5kLmdyaWQoCiAgICBpbnRlcnZhbDYwID0gdW5pcXVlKHdlZWsyMCRpbnRlcnZhbDYwKSwKICAgIHN0YXJ0X3N0YXRpb24gPSB1bmlxdWUocmlkZS5wYW5lbCRzdGFydF9zdGF0aW9uKSkKYGBgCgpgYGB7ciAgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UgLCB3YXJuaW5nPUZBTFNFfQpyaWRlLmFuaW1hdGlvbi5kYXRhIDwtCiAgbXV0YXRlKHdlZWsyMCwgVHJpcF9Db3VudGVyID0gMSkgJT4lCiAgc2VsZWN0KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb24sIHN0YXJ0X2xvbiwgc3RhcnRfbGFuLCBUcmlwX0NvdW50ZXIpICU+JQogICAgcmlnaHRfam9pbih3ZWVrMjAucGFuZWwpICU+JSAKICAgIGdyb3VwX2J5KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb24sIHN0YXJ0X2xvbiwgc3RhcnRfbGFuLCkgJT4lCiAgICBzdW1tYXJpemUoVHJpcF9Db3VudCA9IHN1bShUcmlwX0NvdW50ZXIsIG5hLnJtPVQpKSAlPiUgCiAgICB1bmdyb3VwKCkgJT4lCiAgICBtdXRhdGUoVHJpcHMgPSBjYXNlX3doZW4oVHJpcF9Db3VudCA9PSAwIH4gIjAgdHJpcHMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRyaXBfQ291bnQgPiAwICYgVHJpcF9Db3VudCA8PSAyIH4gIjEtMiB0cmlwcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVHJpcF9Db3VudCA+IDIgJiBUcmlwX0NvdW50IDw9IDUgfiAiMy01IHRyaXBzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUcmlwX0NvdW50ID4gNSAmIFRyaXBfQ291bnQgPD0gMTAgfiAiNi0xMCB0cmlwcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVHJpcF9Db3VudCA+IDEwICYgVHJpcF9Db3VudCA8PSAxNSB+ICIxMS0xNSB0cmlwcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVHJpcF9Db3VudCA+IDE1IH4gIjE2KyB0cmlwcyIpKSAlPiUKICAgIG11dGF0ZShUcmlwcyAgPSBmY3RfcmVsZXZlbChUcmlwcywgIjAgdHJpcHMiLCIxLTIgdHJpcHMiLCIzLTUgdHJpcHMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiNi0xMCB0cmlwcyIsIjYtMTAgdHJpcHMiLCIxNisgdHJpcHMiKSkKYGBgCgpgYGB7ciAgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UgLCB3YXJuaW5nPUZBTFNFfQpyaWRlc2hhcmVfYW5pbWF0aW9uIDwtCiAgZ2dwbG90KCkgKwogICAgZ2VvbV9zZihkYXRhID0gTEFUcmFjdHMgJT4lCiAgICAgICAgICAgICAgc3RfdHJhbnNmb3JtKGNycz00MzI2KSkgKwogICAgZ2VvbV9wb2ludChkYXRhID0gcmlkZS5hbmltYXRpb24uZGF0YSwgCiAgICAgICAgICAgICAgIGFlcyh4ID0gc3RhcnRfbG9uLCB5ID0gc3RhcnRfbGFuLCBmaWxsID0gVHJpcHMpLCBzaXplID0gMC41LCBhbHBoYSA9IDEuNSkrCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gcGFsZXR0ZTUpICsKICAgIGxhYnModGl0bGUgPSAiUmlkZXNoYXJlIHBpY2t1cHMgZm9yIG9uZSBkYXkgaW4gTWF5IDIwMjIsIExBIiwKICAgICAgICAgc3VidGl0bGUgPSAiNjAgbWludXRlIGludGVydmFsczoge2N1cnJlbnRfZnJhbWV9IiwKICAgICAgICAgY2FwdGlvbiA9ICJGaWd1cmUgMy04IikgKwogICAgdHJhbnNpdGlvbl9tYW51YWwoaW50ZXJ2YWw2MCkgKwogICAgbWFwVGhlbWUKCmFuaW1hdGUocmlkZXNoYXJlX2FuaW1hdGlvbiwgZHVyYXRpb249NzUsIHJlbmRlcmVyID0gZ2lmc2tpX3JlbmRlcmVyKCkpCmBgYApgYGB7cn0KcmlkZXNoYXJlX2FuaW1hdGlvbiA8LQogIGdncGxvdCgpICsKICAgIGdlb21fc2YoZGF0YSA9IHJpZGUuYW5pbWF0aW9uLmRhdGEsIGFlcyhmaWxsID0gVHJpcHMpKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlNSkgKwogICAgbGFicyh0aXRsZSA9ICJSaWRlc2hhcmUgcGlja3VwcyBmb3Igb25lIGRheSBpbiBOb3ZlbWJlciAyMDE4IiwKICAgICAgICAgc3VidGl0bGUgPSAiMTUgbWludXRlIGludGVydmFsczoge2N1cnJlbnRfZnJhbWV9IikgKwogICAgdHJhbnNpdGlvbl9tYW51YWwoaW50ZXJ2YWwxNSkgKwogICAgbWFwVGhlbWUKYGBgCgojIDQgTW9kZWxsaW5nCgojIyA0LjEgQnVpbGRpbmcgbW9kZWwKCkluIHRoaXMgc2VjdGlvbiwgZml2ZSBkaWZmZXJlbnQgbGluZWFyIHJlZ3Jlc3Npb25zIGFyZSBlc3RpbWF0ZWQgb24gYHJpZGUuVHJhaW5gLCBlYWNoIHdpdGggZGlmZmVyZW50IGZpeGVkIGVmZmVjdHM6CjEuIGByZWcxYCBmb2N1c2VzIG9uIGp1c3QgdGltZSwgaW5jbHVkaW5nIGhvdXIgZml4ZWQgZWZmZWN0cywgZGF5IG9mIHRoZSB3ZWVrLCBhbmQgYFRlbXBlcmF0dXJlYC4KMi4gYHJlZzJgIGZvY3VzZXMgb24ganVzdCBzcGFjZSBlZmZlY3RzIHdpdGggdGhlIGBzdGFydF9zdGF0aW9uYCBmaXhlZCBlZmZlY3RzLgozLiBgcmVnM2AgaW5jbHVkZXMgYm90aCB0aW1lIGFuZCBzcGFjZSBmaXhlZCBlZmZlY3RzLgo0LiBgcmVnNGAgYWRkcyB0aGUgdGltZSBgbGFnYCBmZWF0dXJlcy4KNS4gYHJlZzVgIGFkZHMgdGhlIGhvbGlkYXkgYGxhZ2AgZmVhdHVyZXMuCgpgYGB7ciBmaXZlX21vZGVscyB9CnJlZzEgPC0gCiAgbG0oVHJpcF9Db3VudCB+ICBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlLCAgZGF0YT1yaWRlLlRyYWluKQpyZWcyIDwtIAogIGxtKFRyaXBfQ291bnQgfiAgc3RhcnRfc3RhdGlvbiArIGRvdHcgKyBUZW1wZXJhdHVyZSwgIGRhdGE9cmlkZS5UcmFpbikKcmVnMyA8LSAKICBsbShUcmlwX0NvdW50IH4gIHN0YXJ0X3N0YXRpb24gKyBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlICsgUHJlY2lwaXRhdGlvbiwgCiAgICAgZGF0YT1yaWRlLlRyYWluKQpyZWc0IDwtIAogIGxtKFRyaXBfQ291bnQgfiAgc3RhcnRfc3RhdGlvbiArICBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlICsgUHJlY2lwaXRhdGlvbiArCiAgICAgICAgICAgICAgICAgICBsYWdIb3VyICsgbGFnMkhvdXJzICtsYWczSG91cnMgKyBsYWcxMkhvdXJzICsgbGFnMWRheSwgCiAgICAgZGF0YT1yaWRlLlRyYWluKQpyZWc1IDwtIAogIGxtKFRyaXBfQ291bnQgfiAgc3RhcnRfc3RhdGlvbiArIGhvdXIoaW50ZXJ2YWw2MCkgKyBkb3R3ICsgVGVtcGVyYXR1cmUgKyBQcmVjaXBpdGF0aW9uICsKICAgICAgICAgICAgICAgICAgIGxhZ0hvdXIgKyBsYWcySG91cnMgK2xhZzNIb3VycyArbGFnMTJIb3VycyArIGxhZzFkYXkgKyBob2xpZGF5LCAjICsgaG9saWRheUxhZyAsCiAgICAgZGF0YT1yaWRlLlRyYWluKQpgYGAKCiMjIDQuMiBQcmVkaWN0IGZvciB0ZXN0IGRhdGEKCk1vZGVscyBidWlsdCBhYm92ZSB3aWxsIGJlIHVzZWQgZm9yIHByZWRpY3RpbmcgZGF0YSBpbiBgcmlkZS5UZXN0YCB3aGljaCBpbmNsdWRlcyAyIHNmIHRpYmJsZXMgKHdpdGggZ2VvbWV0cmllcykuIAoKYGBge3IgbmVzdF9kYXRhICwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CnJpZGUuVGVzdC53ZWVrTmVzdCA8LSAKICByaWRlLlRlc3QgJT4lCiAgbmVzdCgtd2VlaykgCgpyaWRlLlRlc3Qud2Vla05lc3QKYGBgCgpOZXh0LCBhIGZ1bmN0aW9uIGlzIGNyZWF0ZWQgdGhhdCB0YWtlcyBhIHRpYmJsZSwgYGRhdGAgYW5kIGEgcmVncmVzc2lvbiBtb2RlbCwgYGZpdGAgYXMgaXRzIGlucHV0cywgYW5kIG91dHB1dHMgcHJlZGljdGlvbnMgYXMgYHByZWRgLiBFYWNoIHdlZWsgaW4gYHJpZGUuVHJlc3Qud2Vla05lc3RgIGNhbiBiZSBwcmVkaWN0ZWQgYnkgdGhpcyBmdW5jdGlvbi4KCmBgYHtyIHByZWRpY3RfZnVuY3Rpb24gfQptb2RlbF9wcmVkIDwtIGZ1bmN0aW9uKGRhdCwgZml0KXsKICAgcHJlZCA8LSBwcmVkaWN0KGZpdCwgbmV3ZGF0YSA9IGRhdCl9CmBgYAoKVGhlIGNvZGUgYmVsb3cgd2lsbCBsb29wIHRocm91Z2ggZWFjaCBtb2RlbCBmb3IgZWFjaCB0ZXN0aW5nIHdlZWsgYW5kIG11dGF0ZSBzdW1tYXJ5IHN0YXRpc3RpY3MsIGluY2x1ZGluZyB0aGUgcHJlZGljdGlvbnMsIG9ic2VydmVkLCBhYnNvbHV0ZSBlcnJvciwgTUFFIGFuZCBTRCBBRS4KCmBgYHtyIGRvX3ByZWRpY2l0b25zLCByZXN1bHRzPSdoaWRlJywgbWVzc2FnZT1GQUxTRSAsIHdhcm5pbmc9RkFMU0UgfQp3ZWVrX3ByZWRpY3Rpb25zIDwtIAogIHJpZGUuVGVzdC53ZWVrTmVzdCAlPiUgCiAgICBtdXRhdGUoQVRpbWVfRkUgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWcxLCAuZiA9IG1vZGVsX3ByZWQpLAogICAgICAgICAgIEJTcGFjZV9GRSA9IG1hcCgueCA9IGRhdGEsIGZpdCA9IHJlZzIsIC5mID0gbW9kZWxfcHJlZCksCiAgICAgICAgICAgQ1RpbWVfU3BhY2VfRkUgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWczLCAuZiA9IG1vZGVsX3ByZWQpLAogICAgICAgICAgIERUaW1lX1NwYWNlX0ZFX3RpbWVMYWdzID0gbWFwKC54ID0gZGF0YSwgZml0ID0gcmVnNCwgLmYgPSBtb2RlbF9wcmVkKSwKICAgICAgICAgICBFVGltZV9TcGFjZV9GRV90aW1lTGFnc19ob2xpZGF5TGFncyA9IG1hcCgueCA9IGRhdGEsIGZpdCA9IHJlZzUsIC5mID0gbW9kZWxfcHJlZCkpICU+JSAKICAKICAgIGdhdGhlcihSZWdyZXNzaW9uLCBQcmVkaWN0aW9uLCAtZGF0YSwgLXdlZWspICU+JQogICAgbXV0YXRlKE9ic2VydmVkID0gbWFwKGRhdGEsIHB1bGwsIFRyaXBfQ291bnQpLAogICAgICAgICAgIEFic29sdXRlX0Vycm9yID0gbWFwMihPYnNlcnZlZCwgUHJlZGljdGlvbiwgIH4gYWJzKC54IC0gLnkpKSwKICAgICAgICAgICBNQUUgPSBtYXBfZGJsKEFic29sdXRlX0Vycm9yLCBtZWFuLCBuYS5ybSA9IFRSVUUpLAogICAgICAgICAgIHNkX0FFID0gbWFwX2RibChBYnNvbHV0ZV9FcnJvciwgc2QsIG5hLnJtID0gVFJVRSkpCndlZWtfcHJlZGljdGlvbnMKYGBgCgpBIDIgKHdlZWtzKSB0aW1lcyA1IChtb2RlbHMpIHRhYmxlIGlzIGNyZWF0ZWQgYWJvdmUsIGVhY2ggcm93IHByZXNlbnRzIHRoZSBzdGF0aXN0aWNzIG9mIG9uZSB3ZWVrIGluIGByaWRlLlRyZXN0LndlZWtOZXN0YCBwcmVkaWN0ZWQgYnkgYSByZWdyZXNzaW9uIG1vZGVsLgoKIyA1IEV4YW1pbmcgYWNjdXJhY3kKCkluIHRoaXMgc2VjdGlvbiwgTWVhbiBBYnNvbHV0ZSBFcnJvcihNQUUpIGlzIGNhbGN1bGF0ZWQgb24gYHJpZGUuVGVzdGAgZm9yIGVhY2ggbW9kZWwuCgojIyA1LjEgRXhhbWluZSBFcnJvciBNZXRyaWNzIGZvciBBY2N1cmFjeQoKRmlndXJlIDUtMSBzaG93cyBnb29kbmVzcyBvZiBmaXQgYnkgd2VlayBhbmQgbW9kZWwuIFRoZSBNQUUgZm9yIHRoZSB0aW1lIGVmZmVjdHMgbW9kZWwgKGByZWcxYCkgaW4gd2VlayAyMSBpcyBjb21wYXJhYmxlIHRvIHRoZSBtZWFuIG9ic2VydmVkIGBUcmlwX0NvdW50YCBvZiAwLjI1Ni4gV2l0aCBpbmNyZWFzaW5nIHNvcGhpc3RpY2F0aW9uLCB0aGUgbW9kZWwgYmVjb21lcyBtb3JlIGFjY3VyYXRlIGFuZCBtb3JlIGdlbmVyYWxpemFibGUuCgpgYGB7ciBwbG90X2Vycm9yc19ieV9tb2RlbCB9CndlZWtfcHJlZGljdGlvbnMgJT4lCiAgZHBseXI6OnNlbGVjdCh3ZWVrLCBSZWdyZXNzaW9uLCBNQUUpICU+JQogIGdhdGhlcihWYXJpYWJsZSwgTUFFLCAtUmVncmVzc2lvbiwgLXdlZWspICU+JQogIGdncGxvdChhZXMod2VlaywgTUFFKSkgKyAKICAgIGdlb21fYmFyKGFlcyhmaWxsID0gUmVncmVzc2lvbiksIHBvc2l0aW9uID0gImRvZGdlIiwgc3RhdD0iaWRlbnRpdHkiKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlNSkgKwogICAgbGFicyh0aXRsZSA9ICJNZWFuIEFic29sdXRlIEVycm9ycyBieSBtb2RlbCBzcGVjaWZpY2F0aW9uIGFuZCB3ZWVrIiwKICAgICAgICAgY2FwdGlvbiA9ICJGaWd1cmUgNS0xIikgKwogIHBsb3RUaGVtZQpgYGAKCkZvciBlYWNoIG1vZGVsLCBwcmVkaWN0ZWQgYW5kIG9ic2VydmVkIGBUcmlwX0NvdW50YCBpcyB0YWtlbiBvdXQgb2YgdGhlIHNwYXRpYWwgY29udGV4dCBhbmQgdGhlaXIgbWVhbnMgb2YgcGxvdHRlZCBpbiB0aW1lIHNlcmllcyBmb3JtIGJlbG93LiBJdCBzZWVtcyB0aGF0IG1vZGVsIEEgYW5kIEMgaGF2ZSB0aGUgc2FtZSB0aW1lIHRyZW5kIGJlY2F1c2UgdGhlIHNwYXRpYWwgY29udGV4dCBoYXMgYmVlbiByZW1vdmVkLgoKVGhlIHRpbWUgbGFncyBmYWNpbGl0YXRlIHRoZSBjYXBhY2l0eSB0byBwcmVkaWN0IGZvciB0aGUgaGlnaGVzdCBwZWFrcyBhcyBzb3BoaXN0aWNhdGlvbiBpbmNyZWFzZXMuIAoKYGBge3IgZXJyb3JfdnNfYWN0dWFsX3RpbWVzZXJpZXMgLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0Kd2Vla19wcmVkaWN0aW9ucyAlPiUgCiAgICBtdXRhdGUoaW50ZXJ2YWw2MCA9IG1hcChkYXRhLCBwdWxsLCBpbnRlcnZhbDYwKSwKICAgICAgICAgICBzdGFydF9zdGF0aW9uID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0X3N0YXRpb24pKSAlPiUKICAgIGRwbHlyOjpzZWxlY3QoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbiwgT2JzZXJ2ZWQsIFByZWRpY3Rpb24sIFJlZ3Jlc3Npb24pICU+JQogICAgdW5uZXN0KCkgJT4lCiAgICBnYXRoZXIoVmFyaWFibGUsIFZhbHVlLCAtUmVncmVzc2lvbiwgLWludGVydmFsNjAsIC1zdGFydF9zdGF0aW9uKSAlPiUKICAgIGdyb3VwX2J5KFJlZ3Jlc3Npb24sIFZhcmlhYmxlLCBpbnRlcnZhbDYwKSAlPiUKICAgIHN1bW1hcml6ZShWYWx1ZSA9IHN1bShWYWx1ZSkpICU+JQogICAgZ2dwbG90KGFlcyhpbnRlcnZhbDYwLCBWYWx1ZSwgY29sb3VyPVZhcmlhYmxlKSkgKyAKICAgICAgZ2VvbV9saW5lKHNpemUgPSAxLjEpICsgCiAgICAgIGZhY2V0X3dyYXAoflJlZ3Jlc3Npb24sIG5jb2w9MSkgKwogICAgICBsYWJzKHRpdGxlID0gIlByZWRpY3RlZC9PYnNlcnZlZCBiaWtlIHNoYXJlIHRpbWUgc2VyaWVzIiwgc3VidGl0bGUgPSAiTEE7IEEgdGVzdCBzZXQgb2YgMiB3ZWVrcyIsICB4ID0gIkhvdXIiLCB5PSAiU3RhdGlvbiBUcmlwcyIsCiAgICAgICAgIGNhcHRpb24gPSAiRmlndXJlIDUtMiIpICsKICAgICAgcGxvdFRoZW1lCmBgYAoKRmlndXJlIDUtMyBpbGx1c3RyYXRlcyB0aGUgb2JzZXJ2ZWQgYW5kIHByZWRpY3RlZCBkaXN0cmlidXRpb24gZm9yIGRpZmZlcmVudCB0aW1lcyBvZiBkYXkgZHVyaWcgd2Vla2RheXMgYW5kIHdlZWtlbmRzLiBJdCBpcyBjbGVhciB0aGF0IHRoZSBtb2RlbHMgYXJlIHVuZGVyIHByZWRpY3RpbmcgaW4gZ2VuZXJhbC4KCmBgYHtyIG9ic19wcmVkX2FsbCwgd2FybmluZz1GQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCBjYWNoZT1UUlVFfQp3ZWVrX3ByZWRpY3Rpb25zICU+JSAKICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLAogICAgICAgICAgIHN0YXJ0X3N0YXRpb24gPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfc3RhdGlvbiksIAogICAgICAgICAgIHN0YXJ0X2xhbiA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9sYW4pLCAKICAgICAgICAgICBzdGFydF9sb24gPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfbG9uKSwKICAgICAgICAgICBkb3R3ID0gbWFwKGRhdGEsIHB1bGwsIGRvdHcpKSAlPiUKICAgIHNlbGVjdChpbnRlcnZhbDYwLCBzdGFydF9sYW4sIHN0YXJ0X2xvbiwgCiAgICAgICAgICAgc3RhcnRfc3RhdGlvbiwgT2JzZXJ2ZWQsIFByZWRpY3Rpb24sIFJlZ3Jlc3Npb24sCiAgICAgICAgICAgZG90dykgJT4lCiAgICB1bm5lc3QoKSAlPiUKICBmaWx0ZXIoUmVncmVzc2lvbiA9PSAiRVRpbWVfU3BhY2VfRkVfdGltZUxhZ3NfaG9saWRheUxhZ3MiKSU+JQogIG11dGF0ZSh3ZWVrZW5kID0gaWZlbHNlKGRvdHcgJWluJSBjKCJTdW4iLCAiU2F0IiksICJXZWVrZW5kIiwgIldlZWtkYXkiKSwKICAgICAgICAgdGltZV9vZl9kYXkgPSBjYXNlX3doZW4oaG91cihpbnRlcnZhbDYwKSA8IDcgfCBob3VyKGludGVydmFsNjApID4gMTggfiAiT3Zlcm5pZ2h0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSA3ICYgaG91cihpbnRlcnZhbDYwKSA8IDEwIH4gIkFNIFJ1c2giLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDEwICYgaG91cihpbnRlcnZhbDYwKSA8IDE1IH4gIk1pZC1EYXkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDE1ICYgaG91cihpbnRlcnZhbDYwKSA8PSAxOCB+ICJQTSBSdXNoIikpJT4lCiAgZ2dwbG90KCkrCiAgZ2VvbV9wb2ludChhZXMoeD0gT2JzZXJ2ZWQsIHkgPSBQcmVkaWN0aW9uKSkrCiAgICBnZW9tX3Ntb290aChhZXMoeD0gT2JzZXJ2ZWQsIHk9IFByZWRpY3Rpb24pLCBtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBjb2xvciA9ICJyZWQiKSsKICAgIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMCkrCiAgZmFjZXRfZ3JpZCh0aW1lX29mX2RheX53ZWVrZW5kKSsKICBsYWJzKHRpdGxlPSJPYnNlcnZlZCB2cyBQcmVkaWN0ZWQiLAogICAgICAgeD0iT2JzZXJ2ZWQgdHJpcHMiLCAKICAgICAgIHk9IlByZWRpY3RlZCB0cmlwcyIsCiAgICAgICAgIGNhcHRpb24gPSAiRmlndXJlIDUtMyIpKwogIHBsb3RUaGVtZQpgYGAKCiMjIDUuMiBTcGFjZS1UaW1lIEVycm9yIEV2YWx1YXRpb24KCkZpZ3VyZSA1LTQgcGxvdHMgbWVhbiBhYnNvbHV0ZSBlcnJvcnMgb24gbWFwIGJ5IHdlZWtkYXlzL3dlZWtlbmRzIGFuZCB0aW1lIG9mIGEgZGF5LgoKYGBge3Igc3RhdGlvbl9zdW1tYXJ5LCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlID0gRkFMU0UgfQp3ZWVrX3ByZWRpY3Rpb25zICU+JSAKICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLAogICAgICAgICAgIHN0YXJ0X3N0YXRpb24gPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfc3RhdGlvbiksIAogICAgICAgICAgIHN0YXJ0X2xhbiA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9sYW4pLCAKICAgICAgICAgICBzdGFydF9sb24gPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfbG9uKSwKICAgICAgICAgICBkb3R3ID0gbWFwKGRhdGEsIHB1bGwsIGRvdHcpICkgJT4lCiAgICBzZWxlY3QoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbiwgc3RhcnRfbG9uLCAKICAgICAgICAgICBzdGFydF9sYW4sIE9ic2VydmVkLCBQcmVkaWN0aW9uLCBSZWdyZXNzaW9uLAogICAgICAgICAgIGRvdHcpICU+JQogICAgdW5uZXN0KCkgJT4lCiAgZmlsdGVyKFJlZ3Jlc3Npb24gPT0gIkVUaW1lX1NwYWNlX0ZFX3RpbWVMYWdzX2hvbGlkYXlMYWdzIiklPiUKICBtdXRhdGUod2Vla2VuZCA9IGlmZWxzZShkb3R3ICVpbiUgYygiU3VuIiwgIlNhdCIpLCAiV2Vla2VuZCIsICJXZWVrZGF5IiksCiAgICAgICAgIHRpbWVfb2ZfZGF5ID0gY2FzZV93aGVuKGhvdXIoaW50ZXJ2YWw2MCkgPCA3IHwgaG91cihpbnRlcnZhbDYwKSA+IDE4IH4gIk92ZXJuaWdodCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gNyAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxMCB+ICJBTSBSdXNoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxMCAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxNSB+ICJNaWQtRGF5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxNSAmIGhvdXIoaW50ZXJ2YWw2MCkgPD0gMTggfiAiUE0gUnVzaCIpKSAlPiUKICBncm91cF9ieShzdGFydF9zdGF0aW9uLCB3ZWVrZW5kLCB0aW1lX29mX2RheSwgc3RhcnRfbG9uLCBzdGFydF9sYW4pICU+JQogIHN1bW1hcml6ZShNQUUgPSBtZWFuKGFicyhPYnNlcnZlZC1QcmVkaWN0aW9uKSwgbmEucm0gPSBUUlVFKSklPiUKICBnZ3Bsb3QoLikrCiAgZ2VvbV9zZihkYXRhID0gTEFDZW5zdXMsIGNvbG9yID0gImdyZXkiLCBmaWxsID0gInRyYW5zcGFyZW50IikrCiAgZ2VvbV9wb2ludChhZXMoeCA9IHN0YXJ0X2xvbiwgeSA9IHN0YXJ0X2xhbiwgY29sb3IgPSBNQUUpLCAKICAgICAgICAgICAgIGZpbGwgPSAidHJhbnNwYXJlbnQiLCBzaXplID0gMC41LCBhbHBoYSA9IDAuNCkrCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXMoZGlyZWN0aW9uID0gLTEsCiAgZGlzY3JldGUgPSBGQUxTRSwgb3B0aW9uID0gIkQiKSsKICB5bGltKG1pbihkYXRfY2Vuc3VzJHN0YXJ0X2xhbiksIG1heChkYXRfY2Vuc3VzJHN0YXJ0X2xhbikpKwogIHhsaW0obWluKGRhdF9jZW5zdXMkc3RhcnRfbG9uKSwgbWF4KGRhdF9jZW5zdXMkc3RhcnRfbG9uKSkrCiAgZmFjZXRfZ3JpZCh3ZWVrZW5kfnRpbWVfb2ZfZGF5KSsKICBsYWJzKHRpdGxlPSJNZWFuIEFic29sdXRlIEVycm9ycywgVGVzdCBTZXQiLAogICAgICAgICBjYXB0aW9uID0gIkZpZ3VyZSA1LTQiKSsKICBtYXBUaGVtZQogIApgYGAKCiMjIDUuMyBTb2Npby1lY29ub21pYyB2YXJpYWJsZXMKCk9uZSBtaWdodCB0aGluayB0aGF0IHRoZSBzdGF0aW9uIGxvY2F0aW9ucyBwcm9iYWJseSByZWxhdGUgdG8gbGlrZWx5IHVzZXJzLCB3aG8gc2VlbSB0byBiZSBjb21tdXRpbmcgZG93bnRvd24gdG8gdGhlIGxvb3AuIEZpZ3VyZSA1LTQgcGxvdHMgTUFFIGJ5IHRocmVlIHNvY2lvLWVjb25vbWljIHZhcmlhYmxlczogbWVkaWFuIGluY29tZSwgcGVyY2VudCB0YWtpbmcgcHVibGljIHRyYW5zcG9ydGF0aW9uIGFuZCBwZXJjZW50IG9mIHdoaXRlLiBTZXZlcmFsIHN0YXRpb25zIHNob3cgcmVzaXN0YW5jZSB0byB0aGUgbW9kZWxzIC0gdGhleSBoYXZlIGhpZ2ggaW5jb21lLCBsb3cgcHVibGljIHRyYW5zcG9ydGF0aW9uIHVzYWdlIGFuZCBtb3JlIHRoYW4gYSBoYWxmIG9mIHdoaXRlIHBvcHVsYXRpb24uCgpgYGB7ciBzdGF0aW9uX3N1bW1hcnkyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlID0gRkFMU0UgfQp3ZWVrX3ByZWRpY3Rpb25zICU+JSAKICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLAogICAgICAgICAgIHN0YXJ0X3N0YXRpb24gPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfc3RhdGlvbiksIAogICAgICAgICAgIHN0YXJ0X2xhbiA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9sYW4pLCAKICAgICAgICAgICBzdGFydF9sb24gPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfbG9uKSwKICAgICAgICAgICBkb3R3ID0gbWFwKGRhdGEsIHB1bGwsIGRvdHcpLAogICAgICAgICAgIFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucyA9IG1hcChkYXRhLCBwdWxsLCBQZXJjZW50X1Rha2luZ19QdWJsaWNfVHJhbnMpLAogICAgICAgICAgIE1lZF9JbmMgPSBtYXAoZGF0YSwgcHVsbCwgTWVkX0luYyksCiAgICAgICAgICAgUGVyY2VudF9XaGl0ZSA9IG1hcChkYXRhLCBwdWxsLCBQZXJjZW50X1doaXRlKSkgJT4lCiAgICBzZWxlY3QoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbiwgc3RhcnRfbG9uLCAKICAgICAgICAgICBzdGFydF9sYW4sIE9ic2VydmVkLCBQcmVkaWN0aW9uLCBSZWdyZXNzaW9uLAogICAgICAgICAgIGRvdHcsIFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucywgTWVkX0luYywgUGVyY2VudF9XaGl0ZSkgJT4lCiAgICB1bm5lc3QoKSAlPiUKICBmaWx0ZXIoUmVncmVzc2lvbiA9PSAiRVRpbWVfU3BhY2VfRkVfdGltZUxhZ3NfaG9saWRheUxhZ3MiKSU+JQogIG11dGF0ZSh3ZWVrZW5kID0gaWZlbHNlKGRvdHcgJWluJSBjKCJTdW4iLCAiU2F0IiksICJXZWVrZW5kIiwgIldlZWtkYXkiKSwKICAgICAgICAgdGltZV9vZl9kYXkgPSBjYXNlX3doZW4oaG91cihpbnRlcnZhbDYwKSA8IDcgfCBob3VyKGludGVydmFsNjApID4gMTggfiAiT3Zlcm5pZ2h0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSA3ICYgaG91cihpbnRlcnZhbDYwKSA8IDEwIH4gIkFNIFJ1c2giLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDEwICYgaG91cihpbnRlcnZhbDYwKSA8IDE1IH4gIk1pZC1EYXkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDE1ICYgaG91cihpbnRlcnZhbDYwKSA8PSAxOCB+ICJQTSBSdXNoIikpICU+JQogIGZpbHRlcih0aW1lX29mX2RheSA9PSAiQU0gUnVzaCIpICU+JQogIGdyb3VwX2J5KHN0YXJ0X3N0YXRpb24sIFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucywgTWVkX0luYywgUGVyY2VudF9XaGl0ZSkgJT4lCiAgc3VtbWFyaXplKE1BRSA9IG1lYW4oYWJzKE9ic2VydmVkLVByZWRpY3Rpb24pLCBuYS5ybSA9IFRSVUUpKSU+JQogIGdhdGhlcigtc3RhcnRfc3RhdGlvbiwgLU1BRSwga2V5ID0gInZhcmlhYmxlIiwgdmFsdWUgPSAidmFsdWUiKSU+JQogIGdncGxvdCguKSsKICAjZ2VvbV9zZihkYXRhID0gTEFDZW5zdXMsIGNvbG9yID0gImdyZXkiLCBmaWxsID0gInRyYW5zcGFyZW50IikrCiAgZ2VvbV9wb2ludChhZXMoeCA9IHZhbHVlLCB5ID0gTUFFKSwgYWxwaGEgPSAwLjQpKwogIGdlb21fc21vb3RoKGFlcyh4ID0gdmFsdWUsIHkgPSBNQUUpLCBtZXRob2QgPSAibG0iLCBzZT0gRkFMU0UpKwogIGZhY2V0X3dyYXAofnZhcmlhYmxlLCBzY2FsZXMgPSAiZnJlZSIpKwogIGxhYnModGl0bGU9IkVycm9ycyBhcyBhIGZ1bmN0aW9uIG9mIHNvY2lvLWVjb25vbWljIHZhcmlhYmxlcyIsCiAgICAgICB5PSJNZWFuIEFic29sdXRlIEVycm9yIChUcmlwcykiLAogICAgICAgICBjYXB0aW9uID0gIkZpZ3VyZSA1LTQiKSsKICBwbG90VGhlbWUKICAKYGBgCgojIDYgQ3Jvc3MtVmFsaWRhdGlvbgoKQmVsb3csIGEgcGFyYW1ldGVyIGNhbGxlZCBgZml0Q29udHJvbGAgaXMgc2V0IHRvIHNwZWNpZnkgdGhlIG51bWJlciBvZiBrLWZvbGQgcHJ0aXRpb25zIC0gaW4gdGhpcyBjYXNlIDEwMC4gSW4gdGhlIGNvZGUgYmVsb3csIGBzZXQuc2VlZGAgZW5zdXJlcyByZXByb2R1Y2libGUgZm9sZHMuIEFuIG9iamVjdCBgcmVnLmN2YCwgaXMgZXN0aW1hdGVkIHVzaW5nIHRoZSBzYW1lIHJlZ3Jlc3Npb24gYXMgc3BlY2lmaWVkIGluIGByZWcgNGAuCgpgYGB7ciBjdn0KZml0Q29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMTAwKQpzZXQuc2VlZCg4ODg4KQoKcmVnLmN2IDwtIAogIHRyYWluKFRyaXBfQ291bnQgfiAgc3RhcnRfc3RhdGlvbiArICBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlICsgUHJlY2lwaXRhdGlvbiArCiAgICAgICAgICAgICAgICAgICBsYWdIb3VyICsgbGFnMkhvdXJzICtsYWczSG91cnMgKyBsYWcxMkhvdXJzICsgbGFnMWRheSwgCiAgICAgZGF0YT1yaWRlLnBhbmVsLCAKICAgICBtZXRob2QgPSAibG0iLCB0ckNvbnRyb2wgPSBmaXRDb250cm9sLCBuYS5hY3Rpb24gPSBuYS5wYXNzKQoKcmVnLmN2CmBgYAoKVGhlIGNyb3NzLXZhbGlkYXRpb24gb3V0cHV0IHByb3ZpZGVzIHZlcnkgaW1wb3J0YW50IGdvb2RuZXNzIG9mIGZpdCBpbmZvcm1hdGlvbi4gVGhlIGByZXNhbXBsZWAgc2hvd3MgdGhlIGZpcnN0IGZpdmUgb3V0cHV0cyBvZiAxMDAgZm9sZHMuCgpgYGB7cn0KcmVnLmN2JHJlc2FtcGxlWzE6NSxdCmBgYAoKYGBge3IgcGxvdF9jdiwgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UgLCB3YXJuaW5nPUZBTFNFfQpNQUUgPC0gcmVnLmN2JHJlc2FtcGxlWywzXQpkZiA8LSBkYXRhLmZyYW1lKE1BRSkKCmdncGxvdChkZiwgYWVzKHg9TUFFKSkgKwogIGdlb21faGlzdG9ncmFtKGFlcyh5PS4uZGVuc2l0eS4uKSxiaW5zPTM1LCBmaWxsPSIjRkI4QzZGIikgKwogIGxhYnMoCiAgICB4PSJDcm9zcy1WYWxpZGF0aW9uIE1BRSBIaXN0b2dyYW0iLAogICAgeT0iTUFFIiwKICAgIGNhcHRpb24gPSAiRmlndXJlIDYtMSIKICApCnBsb3RUaGVtZQpgYGAKCiMgNyBDb25jbHVzaW9uCgpJIGJlZ2FuIGJ5IGV4cGxvcmluZyBzZXJpYWwgYXV0b2NvcnJlbGF0aW9uIGFuZCBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbiBvZiB0aGUgTEEgTWV0cm8gQmlrZSBTaGFyZSB0cmlwIGRhdGEuIEJpa2Ugc2hhcmUgY291bnRzIERhdGEgYnkgc3RhdGlvbiBzaG93cyBjZXJ0YWluIHRpbWUgcGF0dGVybnMgZHVyaW5nIG9uZSB3ZWVrIG9yIG9uZSBkYXk6IGJpa2VzIGFyZSBoaWdobHkgdXNlZCBvbiB3ZWVrZW5kcyBhbmQgbGVzcyBvbiBXZWRuZXNkYXlzIGFuZCBUaHVyc2RheXMsIDEyOjAwLTE3OjAwIGV2ZXJ5IGRheSBpcyB0aGUgcGVhayB1c2FnZSBwZXJpb2QuIEJpa2Ugc2hhcmUgY291bnRzIGFsc28gc2hvd3Mgc3BhdGlhbCBhZ2dsb21lcmF0aW9uLCB0aGF0IGlzIGNlcnRhaW4gc3RhdGlvbnMgdGVuZCB0byBoYXZlIGhpZ2hlciBiaWtlIHVzYWdlcy4KClRoZW4sIGZpdmUgcmVncmVzc2lvbiBtb2RlbHMgYXJlIGJ1aWx0LiBEaXZpZGUgdGhlIGZpcnN0IHRocmVlIHdlZWtzIGFzIHRoZSB0cmFpbmluZyBzZXQsIGFuZCB0aGUgbmV4dCB0d28gd2Vla3MgYXMgdGhlIHRlc3Qgc2V0LiBCeSBjYWxjdWxhdGluZyB0aGUgcHJlZGljdGlvbiBhbmQgZXJyb3IgZGF0YSBvZiB0aGUgbW9kZWwgaW4gdGhlIHRlc3Qgc2V0LCB0aGUgYWNjdXJhY3kgY2FuIGJlIGV4YW1pbmVkIHRoYXQgdGhlIGVycm9ycyBhcmUgcmVsYXRpdmVseSBzbWFsbC4gCgo=